From 66cc91e58d3b93d8331d1d50c528459e60789ff9 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Mon, 5 Aug 2024 04:34:32 -0500 Subject: [PATCH 01/20] move mcx synthesis method with dirty ancillas to the synthesis library --- qiskit/circuit/library/standard_gates/x.py | 67 +++----------- qiskit/synthesis/__init__.py | 1 + qiskit/synthesis/multi_controlled/__init__.py | 14 +++ .../mcx_with_ancillas_synth.py | 91 +++++++++++++++++++ 4 files changed, 117 insertions(+), 56 deletions(-) create mode 100644 qiskit/synthesis/multi_controlled/__init__.py create mode 100644 qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index f3f7b5ebdb72..a521c6a0b073 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -1528,62 +1528,17 @@ def _define(self): 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 + from qiskit.synthesis.multi_controlled import synth_mcx_n_dirty_ancillas_ICKHC + + qc = synth_mcx_n_dirty_ancillas_ICKHC( + self.num_qubits, + self.num_ctrl_qubits, + self.label, + self.ctrl_state, + self._relative_phase, + self._action_only, + ) + else: qc.rccx(q_controls[0], q_controls[1], q_ancillas[0]) i = 0 diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index cfe5f0b304cb..0a32423b7781 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -173,3 +173,4 @@ two_qubit_cnot_decompose, TwoQubitWeylDecomposition, ) +from .multi_controlled.mcx_with_ancillas_synth import synth_mcx_n_dirty_ancillas_ICKHC diff --git a/qiskit/synthesis/multi_controlled/__init__.py b/qiskit/synthesis/multi_controlled/__init__.py new file mode 100644 index 000000000000..269f33fb33b5 --- /dev/null +++ b/qiskit/synthesis/multi_controlled/__init__.py @@ -0,0 +1,14 @@ +# 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""" +from .mcx_with_ancillas_synth import synth_mcx_n_dirty_ancillas_ICKHC 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..4a1a7bafe228 --- /dev/null +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -0,0 +1,91 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019. +# +# 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. + +from qiskit.circuit.quantumregister import QuantumRegister +from qiskit.circuit.quantumcircuit import QuantumCircuit + + +def synth_mcx_n_dirty_ancillas_ICKHC( + num_qubits, + num_ctrl_qubits=None, + label=None, + ctrl_state=None, + relative_phase: bool = False, + action_only: bool = False, +): + """Synthesis of an MCX gate with n controls and n-2 dirty ancillary qubits, + producing a circuit with at most 8*n-6 CX gates""" + + 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 :] + + 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 From ac6bbde4ef38dbe22bb5f17bb537948de274ea4a Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Mon, 5 Aug 2024 05:20:50 -0500 Subject: [PATCH 02/20] minor lint updates --- qiskit/circuit/library/standard_gates/x.py | 6 ++---- qiskit/synthesis/__init__.py | 2 +- qiskit/synthesis/multi_controlled/__init__.py | 5 +++-- .../multi_controlled/mcx_with_ancillas_synth.py | 10 +++++----- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index a521c6a0b073..90ee3a658fec 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -1528,13 +1528,11 @@ def _define(self): elif not self._relative_phase and self.num_ctrl_qubits == 3: qc._append(C3XGate(), [*q_controls, q_target], []) else: - from qiskit.synthesis.multi_controlled import synth_mcx_n_dirty_ancillas_ICKHC + from qiskit.synthesis.multi_controlled import synth_mcx_n_dirty_ancillas_ickhc - qc = synth_mcx_n_dirty_ancillas_ICKHC( + qc = synth_mcx_n_dirty_ancillas_ickhc( self.num_qubits, self.num_ctrl_qubits, - self.label, - self.ctrl_state, self._relative_phase, self._action_only, ) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index 0a32423b7781..f72de08b0873 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -173,4 +173,4 @@ two_qubit_cnot_decompose, TwoQubitWeylDecomposition, ) -from .multi_controlled.mcx_with_ancillas_synth import synth_mcx_n_dirty_ancillas_ICKHC +from .multi_controlled.mcx_with_ancillas_synth import synth_mcx_n_dirty_ancillas_ickhc diff --git a/qiskit/synthesis/multi_controlled/__init__.py b/qiskit/synthesis/multi_controlled/__init__.py index 269f33fb33b5..34749b0ce273 100644 --- a/qiskit/synthesis/multi_controlled/__init__.py +++ b/qiskit/synthesis/multi_controlled/__init__.py @@ -10,5 +10,6 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Module containing multi-controlled circuits""" -from .mcx_with_ancillas_synth import synth_mcx_n_dirty_ancillas_ICKHC +"""Module containing multi-controlled circuits synthesis""" + +from .mcx_with_ancillas_synth import synth_mcx_n_dirty_ancillas_ickhc diff --git a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py index 4a1a7bafe228..faff00f97179 100644 --- a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -10,15 +10,15 @@ # 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 qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit.quantumcircuit import QuantumCircuit -def synth_mcx_n_dirty_ancillas_ICKHC( - num_qubits, - num_ctrl_qubits=None, - label=None, - ctrl_state=None, +def synth_mcx_n_dirty_ancillas_ickhc( + num_qubits: int, + num_ctrl_qubits: int = None, relative_phase: bool = False, action_only: bool = False, ): From c6ee7df5bcf7b708e0a9bce919dfca69a3492710 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Tue, 6 Aug 2024 02:52:57 -0500 Subject: [PATCH 03/20] move mcx synthesis method with several clean ancillas to the synthesis library --- qiskit/circuit/library/standard_gates/x.py | 16 ++-------- qiskit/synthesis/__init__.py | 5 +++- qiskit/synthesis/multi_controlled/__init__.py | 2 +- .../mcx_with_ancillas_synth.py | 29 +++++++++++++++++++ test/python/circuit/test_controlled_gate.py | 14 +++++++++ 5 files changed, 50 insertions(+), 16 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index 90ee3a658fec..dc11dced4d12 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -1538,20 +1538,8 @@ def _define(self): ) 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]) + from qiskit.synthesis.multi_controlled import synth_mcx_n_clean_ancillas - 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]) - - i -= 1 - - qc.rccx(q_controls[0], q_controls[1], q_ancillas[i]) + qc = synth_mcx_n_clean_ancillas(self.num_qubits, self.num_ctrl_qubits) self.definition = qc diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index f72de08b0873..ad2ae5901a3e 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -173,4 +173,7 @@ two_qubit_cnot_decompose, TwoQubitWeylDecomposition, ) -from .multi_controlled.mcx_with_ancillas_synth import synth_mcx_n_dirty_ancillas_ickhc +from .multi_controlled.mcx_with_ancillas_synth import ( + synth_mcx_n_dirty_ancillas_ickhc, + synth_mcx_n_clean_ancillas, +) diff --git a/qiskit/synthesis/multi_controlled/__init__.py b/qiskit/synthesis/multi_controlled/__init__.py index 34749b0ce273..242fcb2e5388 100644 --- a/qiskit/synthesis/multi_controlled/__init__.py +++ b/qiskit/synthesis/multi_controlled/__init__.py @@ -12,4 +12,4 @@ """Module containing multi-controlled circuits synthesis""" -from .mcx_with_ancillas_synth import synth_mcx_n_dirty_ancillas_ickhc +from .mcx_with_ancillas_synth import synth_mcx_n_dirty_ancillas_ickhc, synth_mcx_n_clean_ancillas diff --git a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py index faff00f97179..e1271a8183e8 100644 --- a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -89,3 +89,32 @@ def synth_mcx_n_dirty_ancillas_ickhc( break return qc + + +def synth_mcx_n_clean_ancillas(num_qubits: int, num_ctrl_qubits: int = None): + """Synthesis of an MCX gate with n controls and n-2 clean ancillary qubits, + producing a circuit with at most 6*n-6 CX gates""" + + 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 diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 707f9d32cb94..91b83aba5d39 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -823,6 +823,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 From 8d74486f90025069e40a542ac71901a141ed904a Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Tue, 6 Aug 2024 03:31:11 -0500 Subject: [PATCH 04/20] move handling up to 3 controls to the synthesis code --- .../multi_controlled/mcx_with_ancillas_synth.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py index e1271a8183e8..c02fa76fa05d 100644 --- a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -31,6 +31,19 @@ def synth_mcx_n_dirty_ancillas_ickhc( 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: + # pylint: disable=cyclic-import + from qiskit.circuit.library.standard_gates.x import C3XGate + + qc._append(C3XGate(), [*q_controls, q_target], []) + return qc + num_ancillas = num_ctrl_qubits - 2 targets = [q_target] + q_ancillas[:num_ancillas][::-1] From bf88fc2d06a21cb37871a1f1353204ad2ec07040 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Tue, 6 Aug 2024 03:32:07 -0500 Subject: [PATCH 05/20] move handling up to 3 controls to the synthesis code --- qiskit/circuit/library/standard_gates/x.py | 31 +++++++--------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index dc11dced4d12..3a56bf939021 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -1513,31 +1513,18 @@ 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: - from qiskit.synthesis.multi_controlled import synth_mcx_n_dirty_ancillas_ickhc - - qc = synth_mcx_n_dirty_ancillas_ickhc( - self.num_qubits, - self.num_ctrl_qubits, - self._relative_phase, - self._action_only, - ) + from qiskit.synthesis.multi_controlled import synth_mcx_n_dirty_ancillas_ickhc - else: + qc = synth_mcx_n_dirty_ancillas_ickhc( + self.num_qubits, + self.num_ctrl_qubits, + self._relative_phase, + self._action_only, + ) + + else: # use clean ancillas from qiskit.synthesis.multi_controlled import synth_mcx_n_clean_ancillas qc = synth_mcx_n_clean_ancillas(self.num_qubits, self.num_ctrl_qubits) From 6bfb5736d29226b8de6bd8f20aa2fd5eddb3f663 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Tue, 6 Aug 2024 05:18:47 -0500 Subject: [PATCH 06/20] handle cyclic imports --- .../multi_controlled/mcx_with_ancillas_synth.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py index c02fa76fa05d..b718cbb96b03 100644 --- a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -12,9 +12,6 @@ """Module containing multi-controlled circuits synthesis with ancillary qubits.""" -from qiskit.circuit.quantumregister import QuantumRegister -from qiskit.circuit.quantumcircuit import QuantumCircuit - def synth_mcx_n_dirty_ancillas_ickhc( num_qubits: int, @@ -25,6 +22,10 @@ def synth_mcx_n_dirty_ancillas_ickhc( """Synthesis of an MCX gate with n controls and n-2 dirty ancillary qubits, producing a circuit with at most 8*n-6 CX gates""" + # pylint: disable=cyclic-import + from qiskit.circuit.quantumregister import QuantumRegister + from qiskit.circuit.quantumcircuit import QuantumCircuit + q = QuantumRegister(num_qubits, name="q") qc = QuantumCircuit(q, name="mcx_vchain") q_controls = q[:num_ctrl_qubits] @@ -108,6 +109,10 @@ def synth_mcx_n_clean_ancillas(num_qubits: int, num_ctrl_qubits: int = None): """Synthesis of an MCX gate with n controls and n-2 clean ancillary qubits, producing a circuit with at most 6*n-6 CX gates""" + # pylint: disable=cyclic-import + from qiskit.circuit.quantumregister import QuantumRegister + from qiskit.circuit.quantumcircuit import QuantumCircuit + q = QuantumRegister(num_qubits, name="q") qc = QuantumCircuit(q, name="mcx_vchain") q_controls = q[:num_ctrl_qubits] From 80cc3c29b6c9a5af975b708eba9cc4b56bd3f49b Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Tue, 6 Aug 2024 09:24:34 -0500 Subject: [PATCH 07/20] add mcx synthesis method with one clean ancilla to the synthesis library --- qiskit/circuit/library/standard_gates/x.py | 43 +------------ qiskit/synthesis/__init__.py | 1 + qiskit/synthesis/multi_controlled/__init__.py | 6 +- .../mcx_with_ancillas_synth.py | 60 ++++++++++++++++++- 4 files changed, 67 insertions(+), 43 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index 3a56bf939021..8027994e0716 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -1371,47 +1371,11 @@ 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=[], - ) + from qiskit.synthesis.multi_controlled import synth_mcx_one_clean_ancilla_bbcdmssw - self.definition = qc + qc = synth_mcx_one_clean_ancilla_bbcdmssw(self.num_qubits, self.num_ctrl_qubits) + self.definition = qc class MCXVChain(MCXGate): @@ -1518,7 +1482,6 @@ def _define(self): from qiskit.synthesis.multi_controlled import synth_mcx_n_dirty_ancillas_ickhc qc = synth_mcx_n_dirty_ancillas_ickhc( - self.num_qubits, self.num_ctrl_qubits, self._relative_phase, self._action_only, diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index ad2ae5901a3e..ba72fba40a0b 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -176,4 +176,5 @@ from .multi_controlled.mcx_with_ancillas_synth import ( synth_mcx_n_dirty_ancillas_ickhc, synth_mcx_n_clean_ancillas, + synth_mcx_one_clean_ancilla_bbcdmssw, ) diff --git a/qiskit/synthesis/multi_controlled/__init__.py b/qiskit/synthesis/multi_controlled/__init__.py index 242fcb2e5388..613a0f47cca9 100644 --- a/qiskit/synthesis/multi_controlled/__init__.py +++ b/qiskit/synthesis/multi_controlled/__init__.py @@ -12,4 +12,8 @@ """Module containing multi-controlled circuits synthesis""" -from .mcx_with_ancillas_synth import synth_mcx_n_dirty_ancillas_ickhc, synth_mcx_n_clean_ancillas +from .mcx_with_ancillas_synth import ( + synth_mcx_n_dirty_ancillas_ickhc, + synth_mcx_n_clean_ancillas, + synth_mcx_one_clean_ancilla_bbcdmssw, +) diff --git a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py index b718cbb96b03..75d153cbfe17 100644 --- a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -12,10 +12,11 @@ """Module containing multi-controlled circuits synthesis with ancillary qubits.""" +from math import ceil + def synth_mcx_n_dirty_ancillas_ickhc( - num_qubits: int, - num_ctrl_qubits: int = None, + num_ctrl_qubits: int, relative_phase: bool = False, action_only: bool = False, ): @@ -26,6 +27,7 @@ def synth_mcx_n_dirty_ancillas_ickhc( from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit.quantumcircuit import QuantumCircuit + 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] @@ -136,3 +138,57 @@ def synth_mcx_n_clean_ancillas(num_qubits: int, num_ctrl_qubits: int = None): qc.rccx(q_controls[0], q_controls[1], q_ancillas[i]) return qc + + +def synth_mcx_one_clean_ancilla_bbcdmssw(num_qubits, num_ctrl: int): + """Implement an MCX gate with n controls using one clean ancilla qubit, + producing a circuit with at most 16*n-8 CX gates.""" + + # pylint: disable=cyclic-import + from qiskit.circuit.quantumregister import QuantumRegister + from qiskit.circuit.quantumcircuit import QuantumCircuit + from qiskit.circuit.library.standard_gates.x import C3XGate, C4XGate + + q = QuantumRegister(num_qubits, name="q") + qc = QuantumCircuit(q, name="mcx_recursive") + + if num_ctrl == 3: + qc._append(C3XGate(), q[:], []) + return qc + + elif num_ctrl == 4: + qc._append(C4XGate(), q[:], []) + return qc + + 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_ancillas_ickhc(num_ctrl_qubits=len(first_half)) + qc_second_half = synth_mcx_n_dirty_ancillas_ickhc(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 From 917dcfd30c07ea0f92c44217a111539f0c8f8265 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Wed, 7 Aug 2024 03:19:57 -0500 Subject: [PATCH 08/20] update input to synth_mcx functions --- qiskit/circuit/library/standard_gates/x.py | 6 +++--- .../mcx_with_ancillas_synth.py | 20 ++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index 8027994e0716..18ed0d4bf2dc 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 @@ -1374,7 +1374,7 @@ def _define(self): from qiskit.synthesis.multi_controlled import synth_mcx_one_clean_ancilla_bbcdmssw - qc = synth_mcx_one_clean_ancilla_bbcdmssw(self.num_qubits, self.num_ctrl_qubits) + qc = synth_mcx_one_clean_ancilla_bbcdmssw(self.num_ctrl_qubits) self.definition = qc @@ -1490,6 +1490,6 @@ def _define(self): else: # use clean ancillas from qiskit.synthesis.multi_controlled import synth_mcx_n_clean_ancillas - qc = synth_mcx_n_clean_ancillas(self.num_qubits, self.num_ctrl_qubits) + qc = synth_mcx_n_clean_ancillas(self.num_ctrl_qubits) self.definition = qc diff --git a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py index 75d153cbfe17..f4473cf6a571 100644 --- a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -107,7 +107,7 @@ def synth_mcx_n_dirty_ancillas_ickhc( return qc -def synth_mcx_n_clean_ancillas(num_qubits: int, num_ctrl_qubits: int = None): +def synth_mcx_n_clean_ancillas(num_ctrl_qubits: int): """Synthesis of an MCX gate with n controls and n-2 clean ancillary qubits, producing a circuit with at most 6*n-6 CX gates""" @@ -115,6 +115,7 @@ def synth_mcx_n_clean_ancillas(num_qubits: int, num_ctrl_qubits: int = None): from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit.quantumcircuit import QuantumCircuit + 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] @@ -140,7 +141,7 @@ def synth_mcx_n_clean_ancillas(num_qubits: int, num_ctrl_qubits: int = None): return qc -def synth_mcx_one_clean_ancilla_bbcdmssw(num_qubits, num_ctrl: int): +def synth_mcx_one_clean_ancilla_bbcdmssw(num_ctrl_qubits: int): """Implement an MCX gate with n controls using one clean ancilla qubit, producing a circuit with at most 16*n-8 CX gates.""" @@ -149,17 +150,22 @@ def synth_mcx_one_clean_ancilla_bbcdmssw(num_qubits, num_ctrl: int): from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit.library.standard_gates.x import C3XGate, C4XGate - q = QuantumRegister(num_qubits, name="q") - qc = QuantumCircuit(q, name="mcx_recursive") - - if num_ctrl == 3: + if num_ctrl_qubits == 3: + q = QuantumRegister(4, name="q") + qc = QuantumCircuit(q, name="mcx_recursive") qc._append(C3XGate(), q[:], []) return qc - elif num_ctrl == 4: + elif num_ctrl_qubits == 4: + q = QuantumRegister(5, name="q") + qc = QuantumCircuit(q, name="mcx_recursive") 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] From b43685cd67e234ad12d7f2f7e19ac3a084b4e140 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Wed, 7 Aug 2024 03:20:51 -0500 Subject: [PATCH 09/20] refactor test for mcx method modes --- test/python/circuit/test_controlled_gate.py | 111 ++++---------------- 1 file changed, 20 insertions(+), 91 deletions(-) diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 91b83aba5d39..6637c8fae99e 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, 7], + 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 From 3e3b73d8d45be9233e91f1941be7dccfe9feb8d4 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Wed, 7 Aug 2024 05:57:19 -0500 Subject: [PATCH 10/20] update circuit names. add references --- .../mcx_with_ancillas_synth.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py index f4473cf6a571..4125272774c2 100644 --- a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -21,7 +21,8 @@ def synth_mcx_n_dirty_ancillas_ickhc( action_only: bool = False, ): """Synthesis of an MCX gate with n controls and n-2 dirty ancillary qubits, - producing a circuit with at most 8*n-6 CX gates""" + producing a circuit with at most 8*n-6 CX gates. + By Iten et. al. http://arxiv.org/abs/1501.06911""" # pylint: disable=cyclic-import from qiskit.circuit.quantumregister import QuantumRegister @@ -29,7 +30,7 @@ def synth_mcx_n_dirty_ancillas_ickhc( num_qubits = 2 * num_ctrl_qubits - 1 q = QuantumRegister(num_qubits, name="q") - qc = QuantumCircuit(q, name="mcx_vchain") + qc = QuantumCircuit(q, name="mcx_n_dirty_ancillas") q_controls = q[:num_ctrl_qubits] q_target = q[num_ctrl_qubits] q_ancillas = q[num_ctrl_qubits + 1 :] @@ -109,7 +110,7 @@ def synth_mcx_n_dirty_ancillas_ickhc( def synth_mcx_n_clean_ancillas(num_ctrl_qubits: int): """Synthesis of an MCX gate with n controls and n-2 clean ancillary qubits, - producing a circuit with at most 6*n-6 CX gates""" + producing a circuit with at most 6*n-6 CX gates.""" # pylint: disable=cyclic-import from qiskit.circuit.quantumregister import QuantumRegister @@ -117,7 +118,7 @@ def synth_mcx_n_clean_ancillas(num_ctrl_qubits: int): num_qubits = 2 * num_ctrl_qubits - 1 q = QuantumRegister(num_qubits, name="q") - qc = QuantumCircuit(q, name="mcx_vchain") + qc = QuantumCircuit(q, name="mcx_n_clean_ancillas") q_controls = q[:num_ctrl_qubits] q_target = q[num_ctrl_qubits] q_ancillas = q[num_ctrl_qubits + 1 :] @@ -143,7 +144,8 @@ def synth_mcx_n_clean_ancillas(num_ctrl_qubits: int): def synth_mcx_one_clean_ancilla_bbcdmssw(num_ctrl_qubits: int): """Implement an MCX gate with n controls using one clean ancilla qubit, - producing a circuit with at most 16*n-8 CX gates.""" + producing a circuit with at most 16*n-8 CX gates. + Based on Barenco et al., 1995. https://arxiv.org/pdf/quant-ph/9503016.pdf""" # pylint: disable=cyclic-import from qiskit.circuit.quantumregister import QuantumRegister @@ -152,19 +154,19 @@ def synth_mcx_one_clean_ancilla_bbcdmssw(num_ctrl_qubits: int): if num_ctrl_qubits == 3: q = QuantumRegister(4, name="q") - qc = QuantumCircuit(q, name="mcx_recursive") + 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_recursive") + 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") + qc = QuantumCircuit(q, name="mcx_one_clean_ancilla") num_ctrl_qubits = len(q) - 1 q_ancilla = q[-1] From ea8afa9e6be48770859242fae20f7fc11fb823e6 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Wed, 7 Aug 2024 05:58:00 -0500 Subject: [PATCH 11/20] reduce num_controls in tests --- test/python/circuit/test_controlled_gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 6637c8fae99e..a517d5d1e4a4 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -508,7 +508,7 @@ def test_multi_controlled_u1_matrix(self, num_controls): self.assertTrue(matrix_equal(simulated, expected)) @combine( - num_controls=[2, 3, 4, 5, 6, 7], + num_controls=[2, 3, 4, 5, 6], mode=[ "noancilla", "recursion", From 7b68e5745541cb732d961f9781ab69214760162a Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Wed, 7 Aug 2024 07:17:16 -0500 Subject: [PATCH 12/20] revert circuit names to old ones --- .../synthesis/multi_controlled/mcx_with_ancillas_synth.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py index 4125272774c2..9997db1d93ca 100644 --- a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -30,7 +30,7 @@ def synth_mcx_n_dirty_ancillas_ickhc( num_qubits = 2 * num_ctrl_qubits - 1 q = QuantumRegister(num_qubits, name="q") - qc = QuantumCircuit(q, name="mcx_n_dirty_ancillas") + 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 :] @@ -118,7 +118,7 @@ def synth_mcx_n_clean_ancillas(num_ctrl_qubits: int): num_qubits = 2 * num_ctrl_qubits - 1 q = QuantumRegister(num_qubits, name="q") - qc = QuantumCircuit(q, name="mcx_n_clean_ancillas") + 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 :] @@ -166,7 +166,7 @@ def synth_mcx_one_clean_ancilla_bbcdmssw(num_ctrl_qubits: int): num_qubits = num_ctrl_qubits + 2 q = QuantumRegister(num_qubits, name="q") - qc = QuantumCircuit(q, name="mcx_one_clean_ancilla") + qc = QuantumCircuit(q, name="mcx_recursive") num_ctrl_qubits = len(q) - 1 q_ancilla = q[-1] From 49c9e5da3c4fee24474438ba176b9020efcb7498 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Thu, 8 Aug 2024 00:02:03 -0500 Subject: [PATCH 13/20] refactor functions names --- qiskit/circuit/library/standard_gates/x.py | 12 ++++++------ qiskit/synthesis/__init__.py | 6 +++--- qiskit/synthesis/multi_controlled/__init__.py | 6 +++--- .../multi_controlled/mcx_with_ancillas_synth.py | 13 +++++++------ 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index 18ed0d4bf2dc..6d2b4aad79d0 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -1372,9 +1372,9 @@ def inverse(self, annotated: bool = False): def _define(self): """Define the MCX gate using recursion.""" - from qiskit.synthesis.multi_controlled import synth_mcx_one_clean_ancilla_bbcdmssw + from qiskit.synthesis.multi_controlled import synth_mcx_1_clean_b95 - qc = synth_mcx_one_clean_ancilla_bbcdmssw(self.num_ctrl_qubits) + qc = synth_mcx_1_clean_b95(self.num_ctrl_qubits) self.definition = qc @@ -1479,17 +1479,17 @@ def _define(self): """Define the MCX gate using a V-chain of CX gates.""" if self._dirty_ancillas: - from qiskit.synthesis.multi_controlled import synth_mcx_n_dirty_ancillas_ickhc + from qiskit.synthesis.multi_controlled import synth_mcx_n_dirty_i15 - qc = synth_mcx_n_dirty_ancillas_ickhc( + qc = synth_mcx_n_dirty_i15( self.num_ctrl_qubits, self._relative_phase, self._action_only, ) else: # use clean ancillas - from qiskit.synthesis.multi_controlled import synth_mcx_n_clean_ancillas + from qiskit.synthesis.multi_controlled import synth_mcx_n_clean_m15 - qc = synth_mcx_n_clean_ancillas(self.num_ctrl_qubits) + 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 ba72fba40a0b..4820e482c285 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -174,7 +174,7 @@ TwoQubitWeylDecomposition, ) from .multi_controlled.mcx_with_ancillas_synth import ( - synth_mcx_n_dirty_ancillas_ickhc, - synth_mcx_n_clean_ancillas, - synth_mcx_one_clean_ancilla_bbcdmssw, + 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 index 613a0f47cca9..0c04823a537a 100644 --- a/qiskit/synthesis/multi_controlled/__init__.py +++ b/qiskit/synthesis/multi_controlled/__init__.py @@ -13,7 +13,7 @@ """Module containing multi-controlled circuits synthesis""" from .mcx_with_ancillas_synth import ( - synth_mcx_n_dirty_ancillas_ickhc, - synth_mcx_n_clean_ancillas, - synth_mcx_one_clean_ancilla_bbcdmssw, + 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 index 9997db1d93ca..923a0da96e68 100644 --- a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -15,7 +15,7 @@ from math import ceil -def synth_mcx_n_dirty_ancillas_ickhc( +def synth_mcx_n_dirty_i15( num_ctrl_qubits: int, relative_phase: bool = False, action_only: bool = False, @@ -108,9 +108,10 @@ def synth_mcx_n_dirty_ancillas_ickhc( return qc -def synth_mcx_n_clean_ancillas(num_ctrl_qubits: int): +def synth_mcx_n_clean_m15(num_ctrl_qubits: int): """Synthesis of an MCX gate with n controls and n-2 clean ancillary qubits, - producing a circuit with at most 6*n-6 CX gates.""" + producing a circuit with at most 6*n-6 CX gates. + Based on Maslov, https://arxiv.org/pdf/1508.03273""" # pylint: disable=cyclic-import from qiskit.circuit.quantumregister import QuantumRegister @@ -142,7 +143,7 @@ def synth_mcx_n_clean_ancillas(num_ctrl_qubits: int): return qc -def synth_mcx_one_clean_ancilla_bbcdmssw(num_ctrl_qubits: int): +def synth_mcx_1_clean_b95(num_ctrl_qubits: int): """Implement an MCX gate with n controls using one clean ancilla qubit, producing a circuit with at most 16*n-8 CX gates. Based on Barenco et al., 1995. https://arxiv.org/pdf/quant-ph/9503016.pdf""" @@ -175,8 +176,8 @@ def synth_mcx_one_clean_ancilla_bbcdmssw(num_ctrl_qubits: int): first_half = [*q[:middle]] second_half = [*q[middle : num_ctrl_qubits - 1], q_ancilla] - qc_first_half = synth_mcx_n_dirty_ancillas_ickhc(num_ctrl_qubits=len(first_half)) - qc_second_half = synth_mcx_n_dirty_ancillas_ickhc(num_ctrl_qubits=len(second_half)) + 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, From 772c6d8be4bb97d9e6e13374165028511a54d3d4 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Thu, 8 Aug 2024 03:32:12 -0500 Subject: [PATCH 14/20] add docstrings --- .../mcx_with_ancillas_synth.py | 60 ++++++++++++++++--- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py index 923a0da96e68..0fdb7f3ff908 100644 --- a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -20,9 +20,27 @@ def synth_mcx_n_dirty_i15( relative_phase: bool = False, action_only: bool = False, ): - """Synthesis of an MCX gate with n controls and n-2 dirty ancillary qubits, - producing a circuit with at most 8*n-6 CX gates. - By Iten et. al. http://arxiv.org/abs/1501.06911""" + """ + Synthesize 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. [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 `_ + """ # pylint: disable=cyclic-import from qiskit.circuit.quantumregister import QuantumRegister @@ -109,9 +127,21 @@ def synth_mcx_n_dirty_i15( def synth_mcx_n_clean_m15(num_ctrl_qubits: int): - """Synthesis of an MCX gate with n controls and n-2 clean ancillary qubits, - producing a circuit with at most 6*n-6 CX gates. - Based on Maslov, https://arxiv.org/pdf/1508.03273""" + """ + Synthesize 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 [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 `_ + """ # pylint: disable=cyclic-import from qiskit.circuit.quantumregister import QuantumRegister @@ -144,9 +174,21 @@ def synth_mcx_n_clean_m15(num_ctrl_qubits: int): def synth_mcx_1_clean_b95(num_ctrl_qubits: int): - """Implement an MCX gate with n controls using one clean ancilla qubit, - producing a circuit with at most 16*n-8 CX gates. - Based on Barenco et al., 1995. https://arxiv.org/pdf/quant-ph/9503016.pdf""" + """ + Synthesize 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. [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 `_ + """ # pylint: disable=cyclic-import from qiskit.circuit.quantumregister import QuantumRegister From 24295dad3ea5a40e953d5176d2f85664817e5e34 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Thu, 8 Aug 2024 06:44:42 -0500 Subject: [PATCH 15/20] update year --- qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py index 0fdb7f3ff908..8d4c30da84de 100644 --- a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2019. +# (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 From 6ea48096d273350255709762ffc6678fe83a330b Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Sat, 10 Aug 2024 23:44:02 -0500 Subject: [PATCH 16/20] add synthesis functions to API docs --- qiskit/synthesis/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index 4820e482c285..611cab97bc02 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 ( From b35081aeff3fb672e3fd8dc3d59ddd6a6720a1f4 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Sat, 10 Aug 2024 23:44:19 -0500 Subject: [PATCH 17/20] add release notes --- ...ynth-mcx-with-ancillas-6a92078d6b0e1de4.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 releasenotes/notes/add-synth-mcx-with-ancillas-6a92078d6b0e1de4.yaml 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..f400d79ba261 --- /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. + - | + 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. + - | + 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. From 769846959e94f0a54b5d8bfa80c00ffbfe591e3d Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Sun, 11 Aug 2024 00:23:04 -0500 Subject: [PATCH 18/20] fix docs --- qiskit/synthesis/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index 611cab97bc02..f1d1e3b28359 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -125,9 +125,9 @@ Multi Controlled Synthesis ========================== -.. autofunction:: synth_mcx_n_dirty_i15, -.. autofunction:: synth_mcx_n_clean_m15, -.. autofunction:: synth_mcx_1_clean_b95, +.. autofunction:: synth_mcx_n_dirty_i15 +.. autofunction:: synth_mcx_n_clean_m15 +.. autofunction:: synth_mcx_1_clean_b95 """ From e9f2a74bb6356ce3d1238daee1f330bb75306d6e Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Sun, 11 Aug 2024 03:29:44 -0500 Subject: [PATCH 19/20] update docs and release notes following review --- .../multi_controlled/mcx_with_ancillas_synth.py | 12 ++++++------ ...add-synth-mcx-with-ancillas-6a92078d6b0e1de4.yaml | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py index 8d4c30da84de..54cc03d547fc 100644 --- a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -22,8 +22,8 @@ def synth_mcx_n_dirty_i15( ): """ Synthesize 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. [1]. + 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. @@ -129,8 +129,8 @@ def synth_mcx_n_dirty_i15( 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 producing a circuit with at most :math:`6 * k - 6` CX gates, - by Maslov [1]. + 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. @@ -176,8 +176,8 @@ def synth_mcx_n_clean_m15(num_ctrl_qubits: int): 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 at most :math:`16 * k - 8` CX gates, - by Barenco et al. [1]. + 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. diff --git a/releasenotes/notes/add-synth-mcx-with-ancillas-6a92078d6b0e1de4.yaml b/releasenotes/notes/add-synth-mcx-with-ancillas-6a92078d6b0e1de4.yaml index f400d79ba261..47719c60b89c 100644 --- a/releasenotes/notes/add-synth-mcx-with-ancillas-6a92078d6b0e1de4.yaml +++ b/releasenotes/notes/add-synth-mcx-with-ancillas-6a92078d6b0e1de4.yaml @@ -4,14 +4,14 @@ 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. + 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. + 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. + by Barenco et al. (arXiv:quant-ph/9503016). From fbea0a9aaabb90d26c5d72142a12c95f0de39a04 Mon Sep 17 00:00:00 2001 From: ShellyGarion Date: Sun, 11 Aug 2024 04:41:20 -0500 Subject: [PATCH 20/20] update imports following review --- qiskit/circuit/library/standard_gates/x.py | 3 +++ .../mcx_with_ancillas_synth.py | 19 +++---------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index 6d2b4aad79d0..3ee5551b599d 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -1372,6 +1372,7 @@ def inverse(self, annotated: bool = False): def _define(self): """Define the MCX gate using recursion.""" + # pylint: disable=cyclic-import from qiskit.synthesis.multi_controlled import synth_mcx_1_clean_b95 qc = synth_mcx_1_clean_b95(self.num_ctrl_qubits) @@ -1479,6 +1480,7 @@ def _define(self): """Define the MCX gate using a V-chain of CX gates.""" if self._dirty_ancillas: + # pylint: disable=cyclic-import from qiskit.synthesis.multi_controlled import synth_mcx_n_dirty_i15 qc = synth_mcx_n_dirty_i15( @@ -1488,6 +1490,7 @@ def _define(self): ) else: # use clean ancillas + # pylint: disable=cyclic-import from qiskit.synthesis.multi_controlled import synth_mcx_n_clean_m15 qc = synth_mcx_n_clean_m15(self.num_ctrl_qubits) diff --git a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py index 54cc03d547fc..cf2325764569 100644 --- a/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py +++ b/qiskit/synthesis/multi_controlled/mcx_with_ancillas_synth.py @@ -13,6 +13,9 @@ """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( @@ -42,10 +45,6 @@ def synth_mcx_n_dirty_i15( `arXiv:1501.06911 `_ """ - # pylint: disable=cyclic-import - from qiskit.circuit.quantumregister import QuantumRegister - from qiskit.circuit.quantumcircuit import QuantumCircuit - num_qubits = 2 * num_ctrl_qubits - 1 q = QuantumRegister(num_qubits, name="q") qc = QuantumCircuit(q, name="mcx_vchain") @@ -60,9 +59,6 @@ def synth_mcx_n_dirty_i15( qc.ccx(q_controls[0], q_controls[1], q_target) return qc elif not relative_phase and num_ctrl_qubits == 3: - # pylint: disable=cyclic-import - from qiskit.circuit.library.standard_gates.x import C3XGate - qc._append(C3XGate(), [*q_controls, q_target], []) return qc @@ -143,10 +139,6 @@ def synth_mcx_n_clean_m15(num_ctrl_qubits: int): `arXiv:1508.03273 `_ """ - # pylint: disable=cyclic-import - from qiskit.circuit.quantumregister import QuantumRegister - from qiskit.circuit.quantumcircuit import QuantumCircuit - num_qubits = 2 * num_ctrl_qubits - 1 q = QuantumRegister(num_qubits, name="q") qc = QuantumCircuit(q, name="mcx_vchain") @@ -190,11 +182,6 @@ def synth_mcx_1_clean_b95(num_ctrl_qubits: int): `arXiv:quant-ph/9503016 `_ """ - # pylint: disable=cyclic-import - from qiskit.circuit.quantumregister import QuantumRegister - from qiskit.circuit.quantumcircuit import QuantumCircuit - from qiskit.circuit.library.standard_gates.x import C3XGate, C4XGate - if num_ctrl_qubits == 3: q = QuantumRegister(4, name="q") qc = QuantumCircuit(q, name="mcx")