From c7e7016419183c788cc384202694ee14baea8baf Mon Sep 17 00:00:00 2001 From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com> Date: Sun, 11 Aug 2024 14:09:27 +0300 Subject: [PATCH] Move mcx synthesis methods with ancillas to the synthesis library (#12904) * move mcx synthesis method with dirty ancillas to the synthesis library * minor lint updates * move mcx synthesis method with several clean ancillas to the synthesis library * move handling up to 3 controls to the synthesis code * move handling up to 3 controls to the synthesis code * handle cyclic imports * add mcx synthesis method with one clean ancilla to the synthesis library * update input to synth_mcx functions * refactor test for mcx method modes * update circuit names. add references * reduce num_controls in tests * revert circuit names to old ones * refactor functions names * add docstrings * update year * add synthesis functions to API docs * add release notes * fix docs * update docs and release notes following review * update imports following review --- qiskit/circuit/library/standard_gates/x.py | 138 ++--------- qiskit/synthesis/__init__.py | 12 + qiskit/synthesis/multi_controlled/__init__.py | 19 ++ .../mcx_with_ancillas_synth.py | 232 ++++++++++++++++++ ...th-mcx-with-ancillas-6a92078d6b0e1de4.yaml | 17 ++ test/python/circuit/test_controlled_gate.py | 125 +++------- 6 files changed, 330 insertions(+), 213 deletions(-) create mode 100644 qiskit/synthesis/multi_controlled/__init__.py create mode 100644 qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py create mode 100644 releasenotes/notes/add-synth-mcx-with-ancillas-6a92078d6b0e1de4.yaml diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index f3f7b5ebdb72..3ee5551b599d 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -13,7 +13,7 @@ """X, CX, CCX and multi-controlled X gates.""" from __future__ import annotations from typing import Optional, Union, Type -from math import ceil, pi +from math import pi import numpy from qiskit.circuit.controlledgate import ControlledGate from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key @@ -1371,47 +1371,12 @@ def inverse(self, annotated: bool = False): def _define(self): """Define the MCX gate using recursion.""" - # pylint: disable=cyclic-import - from qiskit.circuit.quantumcircuit import QuantumCircuit - q = QuantumRegister(self.num_qubits, name="q") - qc = QuantumCircuit(q, name=self.name) - if self.num_qubits == 4: - qc._append(C3XGate(), q[:], []) - self.definition = qc - elif self.num_qubits == 5: - qc._append(C4XGate(), q[:], []) - self.definition = qc - else: - num_ctrl_qubits = len(q) - 1 - q_ancilla = q[-1] - q_target = q[-2] - middle = ceil(num_ctrl_qubits / 2) - first_half = [*q[:middle]] - second_half = [*q[middle : num_ctrl_qubits - 1], q_ancilla] - - qc._append( - MCXVChain(num_ctrl_qubits=len(first_half), dirty_ancillas=True), - qargs=[*first_half, q_ancilla, *q[middle : middle + len(first_half) - 2]], - cargs=[], - ) - qc._append( - MCXVChain(num_ctrl_qubits=len(second_half), dirty_ancillas=True), - qargs=[*second_half, q_target, *q[: len(second_half) - 2]], - cargs=[], - ) - qc._append( - MCXVChain(num_ctrl_qubits=len(first_half), dirty_ancillas=True), - qargs=[*first_half, q_ancilla, *q[middle : middle + len(first_half) - 2]], - cargs=[], - ) - qc._append( - MCXVChain(num_ctrl_qubits=len(second_half), dirty_ancillas=True), - qargs=[*second_half, q_target, *q[: len(second_half) - 2]], - cargs=[], - ) + # pylint: disable=cyclic-import + from qiskit.synthesis.multi_controlled import synth_mcx_1_clean_b95 - self.definition = qc + qc = synth_mcx_1_clean_b95(self.num_ctrl_qubits) + self.definition = qc class MCXVChain(MCXGate): @@ -1513,92 +1478,21 @@ def get_num_ancilla_qubits(num_ctrl_qubits: int, mode: str = "v-chain"): def _define(self): """Define the MCX gate using a V-chain of CX gates.""" - # pylint: disable=cyclic-import - from qiskit.circuit.quantumcircuit import QuantumCircuit - - q = QuantumRegister(self.num_qubits, name="q") - qc = QuantumCircuit(q, name=self.name) - q_controls = q[: self.num_ctrl_qubits] - q_target = q[self.num_ctrl_qubits] - q_ancillas = q[self.num_ctrl_qubits + 1 :] if self._dirty_ancillas: - if self.num_ctrl_qubits < 3: - qc.mcx(q_controls, q_target) - elif not self._relative_phase and self.num_ctrl_qubits == 3: - qc._append(C3XGate(), [*q_controls, q_target], []) - else: - num_ancillas = self.num_ctrl_qubits - 2 - targets = [q_target] + q_ancillas[:num_ancillas][::-1] - - for j in range(2): - for i in range(self.num_ctrl_qubits): # action part - if i < self.num_ctrl_qubits - 2: - if targets[i] != q_target or self._relative_phase: - # gate cancelling - - # cancel rightmost gates of action part - # with leftmost gates of reset part - if self._relative_phase and targets[i] == q_target and j == 1: - qc.cx(q_ancillas[num_ancillas - i - 1], targets[i]) - qc.t(targets[i]) - qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i]) - qc.tdg(targets[i]) - qc.h(targets[i]) - else: - qc.h(targets[i]) - qc.t(targets[i]) - qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i]) - qc.tdg(targets[i]) - qc.cx(q_ancillas[num_ancillas - i - 1], targets[i]) - else: - controls = [ - q_controls[self.num_ctrl_qubits - i - 1], - q_ancillas[num_ancillas - i - 1], - ] - - qc.ccx(controls[0], controls[1], targets[i]) - else: - # implements an optimized toffoli operation - # up to a diagonal gate, akin to lemma 6 of arXiv:1501.06911 - qc.h(targets[i]) - qc.t(targets[i]) - qc.cx(q_controls[self.num_ctrl_qubits - i - 2], targets[i]) - qc.tdg(targets[i]) - qc.cx(q_controls[self.num_ctrl_qubits - i - 1], targets[i]) - qc.t(targets[i]) - qc.cx(q_controls[self.num_ctrl_qubits - i - 2], targets[i]) - qc.tdg(targets[i]) - qc.h(targets[i]) - - break - - for i in range(num_ancillas - 1): # reset part - qc.cx(q_ancillas[i], q_ancillas[i + 1]) - qc.t(q_ancillas[i + 1]) - qc.cx(q_controls[2 + i], q_ancillas[i + 1]) - qc.tdg(q_ancillas[i + 1]) - qc.h(q_ancillas[i + 1]) - - if self._action_only: - qc.ccx(q_controls[-1], q_ancillas[-1], q_target) - - break - else: - qc.rccx(q_controls[0], q_controls[1], q_ancillas[0]) - i = 0 - for j in range(2, self.num_ctrl_qubits - 1): - qc.rccx(q_controls[j], q_ancillas[i], q_ancillas[i + 1]) + # pylint: disable=cyclic-import + from qiskit.synthesis.multi_controlled import synth_mcx_n_dirty_i15 - i += 1 - - qc.ccx(q_controls[-1], q_ancillas[i], q_target) - - for j in reversed(range(2, self.num_ctrl_qubits - 1)): - qc.rccx(q_controls[j], q_ancillas[i - 1], q_ancillas[i]) + qc = synth_mcx_n_dirty_i15( + self.num_ctrl_qubits, + self._relative_phase, + self._action_only, + ) - i -= 1 + else: # use clean ancillas + # pylint: disable=cyclic-import + from qiskit.synthesis.multi_controlled import synth_mcx_n_clean_m15 - qc.rccx(q_controls[0], q_controls[1], q_ancillas[i]) + qc = synth_mcx_n_clean_m15(self.num_ctrl_qubits) self.definition = qc diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index cfe5f0b304cb..f1d1e3b28359 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -122,6 +122,13 @@ .. autofunction:: two_qubit_cnot_decompose +Multi Controlled Synthesis +========================== + +.. autofunction:: synth_mcx_n_dirty_i15 +.. autofunction:: synth_mcx_n_clean_m15 +.. autofunction:: synth_mcx_1_clean_b95 + """ from .evolution import ( @@ -173,3 +180,8 @@ two_qubit_cnot_decompose, TwoQubitWeylDecomposition, ) +from .multi_controlled.mcx_with_ancillas_synth import ( + synth_mcx_n_dirty_i15, + synth_mcx_n_clean_m15, + synth_mcx_1_clean_b95, +) diff --git a/qiskit/synthesis/multi_controlled/__init__.py b/qiskit/synthesis/multi_controlled/__init__.py new file mode 100644 index 000000000000..0c04823a537a --- /dev/null +++ b/qiskit/synthesis/multi_controlled/__init__.py @@ -0,0 +1,19 @@ +# 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 .mcx_with_ancillas_synth import ( + synth_mcx_n_dirty_i15, + synth_mcx_n_clean_m15, + synth_mcx_1_clean_b95, +) diff --git a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py new file mode 100644 index 000000000000..cf2325764569 --- /dev/null +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -0,0 +1,232 @@ +# 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 with ancillary qubits.""" + +from math import ceil +from qiskit.circuit.quantumregister import QuantumRegister +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.circuit.library.standard_gates.x import C3XGate, C4XGate + + +def synth_mcx_n_dirty_i15( + num_ctrl_qubits: int, + relative_phase: bool = False, + action_only: bool = False, +): + """ + Synthesize a multi-controlled X gate with :math:`k` controls using :math:`k - 2` + dirty ancillary qubits producing a circuit with :math:`2 * k - 1` qubits and at most + :math:`8 * k - 6` CX gates, by Iten et. al. [1]. + + Args: + num_ctrl_qubits: The number of control qubits. + + relative_phase: when set to ``True``, the method applies the optimized multi-controlled X gate + up to a relative phase, in a way that, by lemma 8 of [1], the relative + phases of the ``action part`` cancel out with the phases of the ``reset part``. + + action_only: when set to ``True``, the method applies only the ``action part`` of lemma 8 of [1]. + + Returns: + The synthesized quantum circuit. + + References: + 1. Iten et. al., *Quantum Circuits for Isometries*, Phys. Rev. A 93, 032318 (2016), + `arXiv:1501.06911 `_ + """ + + num_qubits = 2 * num_ctrl_qubits - 1 + q = QuantumRegister(num_qubits, name="q") + qc = QuantumCircuit(q, name="mcx_vchain") + q_controls = q[:num_ctrl_qubits] + q_target = q[num_ctrl_qubits] + q_ancillas = q[num_ctrl_qubits + 1 :] + + if num_ctrl_qubits == 1: + qc.cx(q_controls, q_target) + return qc + elif num_ctrl_qubits == 2: + qc.ccx(q_controls[0], q_controls[1], q_target) + return qc + elif not relative_phase and num_ctrl_qubits == 3: + qc._append(C3XGate(), [*q_controls, q_target], []) + return qc + + num_ancillas = num_ctrl_qubits - 2 + targets = [q_target] + q_ancillas[:num_ancillas][::-1] + + for j in range(2): + for i in range(num_ctrl_qubits): # action part + if i < num_ctrl_qubits - 2: + if targets[i] != q_target or relative_phase: + # gate cancelling + + # cancel rightmost gates of action part + # with leftmost gates of reset part + if relative_phase and targets[i] == q_target and j == 1: + qc.cx(q_ancillas[num_ancillas - i - 1], targets[i]) + qc.t(targets[i]) + qc.cx(q_controls[num_ctrl_qubits - i - 1], targets[i]) + qc.tdg(targets[i]) + qc.h(targets[i]) + else: + qc.h(targets[i]) + qc.t(targets[i]) + qc.cx(q_controls[num_ctrl_qubits - i - 1], targets[i]) + qc.tdg(targets[i]) + qc.cx(q_ancillas[num_ancillas - i - 1], targets[i]) + else: + controls = [ + q_controls[num_ctrl_qubits - i - 1], + q_ancillas[num_ancillas - i - 1], + ] + + qc.ccx(controls[0], controls[1], targets[i]) + else: + # implements an optimized toffoli operation + # up to a diagonal gate, akin to lemma 6 of arXiv:1501.06911 + qc.h(targets[i]) + qc.t(targets[i]) + qc.cx(q_controls[num_ctrl_qubits - i - 2], targets[i]) + qc.tdg(targets[i]) + qc.cx(q_controls[num_ctrl_qubits - i - 1], targets[i]) + qc.t(targets[i]) + qc.cx(q_controls[num_ctrl_qubits - i - 2], targets[i]) + qc.tdg(targets[i]) + qc.h(targets[i]) + + break + + for i in range(num_ancillas - 1): # reset part + qc.cx(q_ancillas[i], q_ancillas[i + 1]) + qc.t(q_ancillas[i + 1]) + qc.cx(q_controls[2 + i], q_ancillas[i + 1]) + qc.tdg(q_ancillas[i + 1]) + qc.h(q_ancillas[i + 1]) + + if action_only: + qc.ccx(q_controls[-1], q_ancillas[-1], q_target) + + break + + return qc + + +def synth_mcx_n_clean_m15(num_ctrl_qubits: int): + """ + Synthesize a multi-controlled X gate with :math:`k` controls using :math:`k - 2` + clean ancillary qubits with producing a circuit with :math:`2 * k - 1` qubits + and at most :math:`6 * k - 6` CX gates, by Maslov [1]. + + Args: + num_ctrl_qubits: The number of control qubits. + + Returns: + The synthesized quantum circuit. + + References: + 1. Maslov., Phys. Rev. A 93, 022311 (2016), + `arXiv:1508.03273 `_ + """ + + num_qubits = 2 * num_ctrl_qubits - 1 + q = QuantumRegister(num_qubits, name="q") + qc = QuantumCircuit(q, name="mcx_vchain") + q_controls = q[:num_ctrl_qubits] + q_target = q[num_ctrl_qubits] + q_ancillas = q[num_ctrl_qubits + 1 :] + + qc.rccx(q_controls[0], q_controls[1], q_ancillas[0]) + i = 0 + for j in range(2, num_ctrl_qubits - 1): + qc.rccx(q_controls[j], q_ancillas[i], q_ancillas[i + 1]) + + i += 1 + + qc.ccx(q_controls[-1], q_ancillas[i], q_target) + + for j in reversed(range(2, num_ctrl_qubits - 1)): + qc.rccx(q_controls[j], q_ancillas[i - 1], q_ancillas[i]) + + i -= 1 + + qc.rccx(q_controls[0], q_controls[1], q_ancillas[i]) + + return qc + + +def synth_mcx_1_clean_b95(num_ctrl_qubits: int): + """ + Synthesize a multi-controlled X gate with :math:`k` controls using a single + clean ancillary qubit producing a circuit with :math:`k + 2` qubits and at most + :math:`16 * k - 8` CX gates, by Barenco et al. [1]. + + Args: + num_ctrl_qubits: The number of control qubits. + + Returns: + The synthesized quantum circuit. + + References: + 1. Barenco et. al., Phys.Rev. A52 3457 (1995), + `arXiv:quant-ph/9503016 `_ + """ + + if num_ctrl_qubits == 3: + q = QuantumRegister(4, name="q") + qc = QuantumCircuit(q, name="mcx") + qc._append(C3XGate(), q[:], []) + return qc + + elif num_ctrl_qubits == 4: + q = QuantumRegister(5, name="q") + qc = QuantumCircuit(q, name="mcx") + qc._append(C4XGate(), q[:], []) + return qc + + num_qubits = num_ctrl_qubits + 2 + q = QuantumRegister(num_qubits, name="q") + qc = QuantumCircuit(q, name="mcx_recursive") + + num_ctrl_qubits = len(q) - 1 + q_ancilla = q[-1] + q_target = q[-2] + middle = ceil(num_ctrl_qubits / 2) + first_half = [*q[:middle]] + second_half = [*q[middle : num_ctrl_qubits - 1], q_ancilla] + + qc_first_half = synth_mcx_n_dirty_i15(num_ctrl_qubits=len(first_half)) + qc_second_half = synth_mcx_n_dirty_i15(num_ctrl_qubits=len(second_half)) + + qc.append( + qc_first_half, + qargs=[*first_half, q_ancilla, *q[middle : middle + len(first_half) - 2]], + cargs=[], + ) + qc.append( + qc_second_half, + qargs=[*second_half, q_target, *q[: len(second_half) - 2]], + cargs=[], + ) + qc.append( + qc_first_half, + qargs=[*first_half, q_ancilla, *q[middle : middle + len(first_half) - 2]], + cargs=[], + ) + qc.append( + qc_second_half, + qargs=[*second_half, q_target, *q[: len(second_half) - 2]], + cargs=[], + ) + + return qc diff --git a/releasenotes/notes/add-synth-mcx-with-ancillas-6a92078d6b0e1de4.yaml b/releasenotes/notes/add-synth-mcx-with-ancillas-6a92078d6b0e1de4.yaml new file mode 100644 index 000000000000..47719c60b89c --- /dev/null +++ b/releasenotes/notes/add-synth-mcx-with-ancillas-6a92078d6b0e1de4.yaml @@ -0,0 +1,17 @@ +--- +features_synthesis: + - | + Add a synthesis function :func:`.synth_mcx_n_dirty_i15` that + synthesizes a multi-controlled X gate with :math:`k` controls using :math:`k - 2` + dirty ancillary qubits producing a circuit with at most :math:`8 * k - 6` CX gates, + by Iten et. al. (arXiv:1501.06911). + - | + Add a synthesis function :func:`.synth_mcx_n_clean_m15` that + synthesizes a multi-controlled X gate with :math:`k` controls using :math:`k - 2` + clean ancillary qubits producing a circuit with at most :math:`6 * k - 6` CX gates, + by Maslov (arXiv:1508.03273). + - | + Add a synthesis function :func:`.synth_mcx_1_clean_b95` that + synthesizes a multi-controlled X gate with :math:`k` controls using a single + clean ancillary qubit producing a circuit with at most :math:`16 * k - 8` CX gates, + by Barenco et al. (arXiv:quant-ph/9503016). diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 707f9d32cb94..a517d5d1e4a4 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -507,89 +507,37 @@ def test_multi_controlled_u1_matrix(self, num_controls): with self.subTest(msg=f"control state = {ctrl_state}"): self.assertTrue(matrix_equal(simulated, expected)) - @data(1, 2, 3, 4) - def test_multi_control_toffoli_matrix_clean_ancillas(self, num_controls): - """Test the multi-control Toffoli gate with clean ancillas. - - Based on the test moved here from Aqua: - https://github.com/Qiskit/qiskit-aqua/blob/769ca8f/test/aqua/test_mct.py - """ - # set up circuit - q_controls = QuantumRegister(num_controls) - q_target = QuantumRegister(1) - qc = QuantumCircuit(q_controls, q_target) - - if num_controls > 2: - num_ancillas = num_controls - 2 - q_ancillas = QuantumRegister(num_controls) - qc.add_register(q_ancillas) - else: - num_ancillas = 0 - q_ancillas = None - - # apply hadamard on control qubits and toffoli gate - qc.mcx(q_controls, q_target[0], q_ancillas, mode="basic") - - # obtain unitary for circuit - simulated = Operator(qc).data - - # compare to expectation - if num_ancillas > 0: - simulated = simulated[: 2 ** (num_controls + 1), : 2 ** (num_controls + 1)] - - base = XGate().to_matrix() - expected = _compute_control_matrix(base, num_controls) - self.assertTrue(matrix_equal(simulated, expected)) - - @data(1, 2, 3, 4, 5) - def test_multi_control_toffoli_matrix_basic_dirty_ancillas(self, num_controls): - """Test the multi-control Toffoli gate with dirty ancillas (basic-dirty-ancilla). - - Based on the test moved here from Aqua: - https://github.com/Qiskit/qiskit-aqua/blob/769ca8f/test/aqua/test_mct.py - """ + @combine( + num_controls=[2, 3, 4, 5, 6], + mode=[ + "noancilla", + "recursion", + "v-chain", + "v-chain-dirty", + "advanced", + "basic", + "basic-dirty-ancilla", + ], + ) + def test_multi_control_toffoli_matrix_advanced_num_ancillas(self, num_controls, mode): + """Test the multi-control Toffoli gate methods with and w/o ancillas.""" q_controls = QuantumRegister(num_controls) q_target = QuantumRegister(1) qc = QuantumCircuit(q_controls, q_target) q_ancillas = None - if num_controls <= 2: + if mode == "noancilla": num_ancillas = 0 - else: - num_ancillas = num_controls - 2 + if mode in ["recursion", "advanced"]: + num_ancillas = int(num_controls > 4) q_ancillas = QuantumRegister(num_ancillas) qc.add_register(q_ancillas) - - qc.mcx(q_controls, q_target[0], q_ancillas, mode="basic-dirty-ancilla") - - simulated = Operator(qc).data - if num_ancillas > 0: - simulated = simulated[: 2 ** (num_controls + 1), : 2 ** (num_controls + 1)] - - base = XGate().to_matrix() - expected = _compute_control_matrix(base, num_controls) - self.assertTrue(matrix_equal(simulated, expected, atol=1e-8)) - - @data(1, 2, 3, 4, 5) - def test_multi_control_toffoli_matrix_advanced_dirty_ancillas(self, num_controls): - """Test the multi-control Toffoli gate with dirty ancillas (advanced). - - Based on the test moved here from Aqua: - https://github.com/Qiskit/qiskit-aqua/blob/769ca8f/test/aqua/test_mct.py - """ - q_controls = QuantumRegister(num_controls) - q_target = QuantumRegister(1) - qc = QuantumCircuit(q_controls, q_target) - - q_ancillas = None - if num_controls <= 4: - num_ancillas = 0 - else: - num_ancillas = 1 + if mode[:7] == "v-chain" or mode[:5] == "basic": + num_ancillas = max(0, num_controls - 2) q_ancillas = QuantumRegister(num_ancillas) qc.add_register(q_ancillas) - qc.mcx(q_controls, q_target[0], q_ancillas, mode="advanced") + qc.mcx(q_controls, q_target[0], q_ancillas, mode=mode) simulated = Operator(qc).data if num_ancillas > 0: @@ -599,25 +547,6 @@ def test_multi_control_toffoli_matrix_advanced_dirty_ancillas(self, num_controls expected = _compute_control_matrix(base, num_controls) self.assertTrue(matrix_equal(simulated, expected, atol=1e-8)) - @data(1, 2, 3) - def test_multi_control_toffoli_matrix_noancilla_dirty_ancillas(self, num_controls): - """Test the multi-control Toffoli gate with dirty ancillas (noancilla). - - Based on the test moved here from Aqua: - https://github.com/Qiskit/qiskit-aqua/blob/769ca8f/test/aqua/test_mct.py - """ - q_controls = QuantumRegister(num_controls) - q_target = QuantumRegister(1) - qc = QuantumCircuit(q_controls, q_target) - - qc.mcx(q_controls, q_target[0], None, mode="noancilla") - - simulated = Operator(qc) - - base = XGate().to_matrix() - expected = _compute_control_matrix(base, num_controls) - self.assertTrue(matrix_equal(simulated, expected, atol=1e-8)) - def test_mcsu2_real_diagonal(self): """Test mcsu2_real_diagonal""" num_ctrls = 6 @@ -823,6 +752,20 @@ def test_mcxvchain_dirty_ancilla_cx_count(self, num_ctrl_qubits): self.assertLessEqual(cx_count, 8 * num_ctrl_qubits - 6) + @data(5, 10, 15) + def test_mcxvchain_clean_ancilla_cx_count(self, num_ctrl_qubits): + """Test if cx count of the v-chain mcx with clean ancilla + is less than upper bound.""" + mcx_vchain = MCXVChain(num_ctrl_qubits, dirty_ancillas=False) + qc = QuantumCircuit(mcx_vchain.num_qubits) + + qc.append(mcx_vchain, list(range(mcx_vchain.num_qubits))) + + tr_mcx_vchain = transpile(qc, basis_gates=["u", "cx"]) + cx_count = tr_mcx_vchain.count_ops()["cx"] + + self.assertLessEqual(cx_count, 6 * num_ctrl_qubits - 6) + @data(7, 10, 15) def test_mcxrecursive_clean_ancilla_cx_count(self, num_ctrl_qubits): """Test if cx count of the mcx with one clean ancilla