Skip to content

Commit

Permalink
Adding MCX synthesis plugins (#12961)
Browse files Browse the repository at this point in the history
* moving high-level-synthesis plugins to a separate file

* Adding the remaining MCX synthesis functions and exposting all of them as HLS synthesis plugins.

* adding entry points for MCX plugins

* adding documentation section for MCX plugins

* renaming file

* Adding pending deprecation warnings

* placeholder for MCX plugin tests

* adding flag pending=True to deprecate

* changing checks from isinstance to name-based: a CCCX gate is called mcx but is not an MCXGate

* futher exposing C3X and C4X synthesis

* updating MCX synthesis functions to avoid returning C3X and C4X gates

For better or for worse, C3X and C4X gates have name 'mcx', and
therefore are caught by mcx plugins. We need to avoid recursion
with HLS calling an MCX-synthesis plugin for a C3X-gate, which
in turns returns a C3X-gate.

* fix compose to append

* renaming synthesized circuits for c3x and for c4x back to 'mcx' to avoid qasm changes

* test qasm fixes

* randomly spotted typo

* fixing how QuantumCircuit.decompose works in the presence of gates_to_decompose

* updating MCX plugins to check isinstance

* fixing test

* pylint fixes

* properly fixing test

* additional tests

* adding new synthesis functions to synthesis docs

* release notes

* docstrings improvements followin review

* Adding refernce to Vale et al paper for the MCXPhase gate implementation

* fixes to deprecation warnings and adding deprecation for get_num_ancilla_qubits

* docstring fixes

* renaming mcphase to v24

* removing unncessary checks

* addressing the rest of review comments

* and of course updating qasm checking after we've slightly changed the decomposition

* yet another renaming

* Update qiskit/circuit/library/standard_gates/x.py

Co-authored-by: Julien Gacon <[email protected]>

* Update qiskit/circuit/library/standard_gates/x.py

Co-authored-by: Julien Gacon <[email protected]>

* Update releasenotes/notes/add-mcx-plugins-85e5b248692a36db.yaml

Co-authored-by: Julien Gacon <[email protected]>

* release notes

* formatting

* fixing docs

* removing references from the first sentence of plugin descriptions

---------

Co-authored-by: Julien Gacon <[email protected]>
  • Loading branch information
alexanderivrii and Cryoris authored Aug 22, 2024
1 parent fb81116 commit 072548f
Show file tree
Hide file tree
Showing 14 changed files with 1,400 additions and 679 deletions.
40 changes: 23 additions & 17 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,23 +75,29 @@ aqc = "qiskit.transpiler.passes.synthesis.aqc_plugin:AQCSynthesisPlugin"
sk = "qiskit.transpiler.passes.synthesis.solovay_kitaev_synthesis:SolovayKitaevSynthesis"

[project.entry-points."qiskit.synthesis"]
"clifford.default" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:DefaultSynthesisClifford"
"clifford.ag" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:AGSynthesisClifford"
"clifford.bm" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:BMSynthesisClifford"
"clifford.greedy" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:GreedySynthesisClifford"
"clifford.layers" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:LayerSynthesisClifford"
"clifford.lnn" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:LayerLnnSynthesisClifford"
"linear_function.default" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:DefaultSynthesisLinearFunction"
"linear_function.kms" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:KMSSynthesisLinearFunction"
"linear_function.pmh" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:PMHSynthesisLinearFunction"
"permutation.default" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:BasicSynthesisPermutation"
"permutation.kms" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:KMSSynthesisPermutation"
"permutation.basic" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:BasicSynthesisPermutation"
"permutation.acg" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:ACGSynthesisPermutation"
"qft.full" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:QFTSynthesisFull"
"qft.line" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:QFTSynthesisLine"
"qft.default" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:QFTSynthesisFull"
"permutation.token_swapper" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:TokenSwapperSynthesisPermutation"
"clifford.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:DefaultSynthesisClifford"
"clifford.ag" = "qiskit.transpiler.passes.synthesis.hls_plugins:AGSynthesisClifford"
"clifford.bm" = "qiskit.transpiler.passes.synthesis.hls_plugins:BMSynthesisClifford"
"clifford.greedy" = "qiskit.transpiler.passes.synthesis.hls_plugins:GreedySynthesisClifford"
"clifford.layers" = "qiskit.transpiler.passes.synthesis.hls_plugins:LayerSynthesisClifford"
"clifford.lnn" = "qiskit.transpiler.passes.synthesis.hls_plugins:LayerLnnSynthesisClifford"
"linear_function.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:DefaultSynthesisLinearFunction"
"linear_function.kms" = "qiskit.transpiler.passes.synthesis.hls_plugins:KMSSynthesisLinearFunction"
"linear_function.pmh" = "qiskit.transpiler.passes.synthesis.hls_plugins:PMHSynthesisLinearFunction"
"mcx.n_dirty_i15" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCXSynthesisNDirtyI15"
"mcx.n_clean_m15" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCXSynthesisNCleanM15"
"mcx.1_clean_b95" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCXSynthesis1CleanB95"
"mcx.gray_code" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCXSynthesisGrayCode"
"mcx.noaux_v24" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCXSynthesisNoAuxV24"
"mcx.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:MCXSynthesisDefault"
"permutation.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:BasicSynthesisPermutation"
"permutation.kms" = "qiskit.transpiler.passes.synthesis.hls_plugins:KMSSynthesisPermutation"
"permutation.basic" = "qiskit.transpiler.passes.synthesis.hls_plugins:BasicSynthesisPermutation"
"permutation.acg" = "qiskit.transpiler.passes.synthesis.hls_plugins:ACGSynthesisPermutation"
"qft.full" = "qiskit.transpiler.passes.synthesis.hls_plugins:QFTSynthesisFull"
"qft.line" = "qiskit.transpiler.passes.synthesis.hls_plugins:QFTSynthesisLine"
"qft.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:QFTSynthesisFull"
"permutation.token_swapper" = "qiskit.transpiler.passes.synthesis.hls_plugins:TokenSwapperSynthesisPermutation"

[project.entry-points."qiskit.transpiler.init"]
default = "qiskit.transpiler.preset_passmanagers.builtin_plugins:DefaultInitPassManager"
Expand Down
77 changes: 53 additions & 24 deletions qiskit/circuit/library/standard_gates/x.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit._utils import _ctrl_state_to_int, with_gate_array, with_controlled_gate_array
from qiskit._accelerate.circuit import StandardGate
from qiskit.utils.deprecation import deprecate_func

_X_ARRAY = [[0, 1], [1, 0]]
_SX_ARRAY = [[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]]
Expand Down Expand Up @@ -1168,6 +1169,19 @@ def inverse(self, annotated: bool = False):
return MCXGate(num_ctrl_qubits=self.num_ctrl_qubits, ctrl_state=self.ctrl_state)

@staticmethod
@deprecate_func(
additional_msg=(
"For an MCXGate it is no longer possible to know the number of ancilla qubits "
"that would be eventually used by the transpiler when the gate is created. "
"Instead, it is recommended to use MCXGate and let HighLevelSynthesis choose "
"the best synthesis method depending on the number of ancilla qubits available. "
"However, if a specific synthesis method using a specific number of ancilla "
"qubits is require, one can create a custom gate by calling the corresponding "
"synthesis function directly."
),
since="1.3",
pending=True,
)
def get_num_ancilla_qubits(num_ctrl_qubits: int, mode: str = "noancilla") -> int:
"""Get the number of required ancilla qubits without instantiating the class.
Expand All @@ -1185,23 +1199,10 @@ def get_num_ancilla_qubits(num_ctrl_qubits: int, mode: str = "noancilla") -> int
def _define(self):
"""This definition is based on MCPhaseGate implementation."""
# pylint: disable=cyclic-import
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.synthesis.multi_controlled import synth_mcx_noaux_v24

q = QuantumRegister(self.num_qubits, name="q")
qc = QuantumCircuit(q)
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:
q_controls = list(range(self.num_ctrl_qubits))
q_target = self.num_ctrl_qubits
qc.h(q_target)
qc.mcp(numpy.pi, q_controls, q_target)
qc.h(q_target)
self.definition = qc
qc = synth_mcx_noaux_v24(self.num_ctrl_qubits)
self.definition = qc

@property
def num_ancilla_qubits(self):
Expand Down Expand Up @@ -1280,6 +1281,17 @@ def __new__(
return gate
return super().__new__(cls)

@deprecate_func(
additional_msg=(
"It is recommended to use MCXGate and let HighLevelSynthesis choose "
"the best synthesis method depending on the number of ancilla qubits available. "
"If this specific synthesis method is required, one can specify it using the "
"high-level-synthesis plugin `gray_code` for MCX gates, or, alternatively, "
"one can use synth_mcx_gray_code to construct the gate directly."
),
since="1.3",
pending=True,
)
def __init__(
self,
num_ctrl_qubits: int,
Expand All @@ -1305,15 +1317,9 @@ def inverse(self, annotated: bool = False):
def _define(self):
"""Define the MCX gate using the Gray code."""
# pylint: disable=cyclic-import
from qiskit.circuit.quantumcircuit import QuantumCircuit
from .u1 import MCU1Gate
from .h import HGate
from qiskit.synthesis.multi_controlled import synth_mcx_gray_code

q = QuantumRegister(self.num_qubits, name="q")
qc = QuantumCircuit(q, name=self.name)
qc._append(HGate(), [q[-1]], [])
qc._append(MCU1Gate(numpy.pi, num_ctrl_qubits=self.num_ctrl_qubits), q[:], [])
qc._append(HGate(), [q[-1]], [])
qc = synth_mcx_gray_code(self.num_ctrl_qubits)
self.definition = qc


Expand All @@ -1330,6 +1336,17 @@ class MCXRecursive(MCXGate):
2. Iten et al., 2015. https://arxiv.org/abs/1501.06911
"""

@deprecate_func(
additional_msg=(
"It is recommended to use MCXGate and let HighLevelSynthesis choose "
"the best synthesis method depending on the number of ancilla qubits available. "
"If this specific synthesis method is required, one can specify it using the "
"high-level-synthesis plugin '1_clean_b95' for MCX gates, or, alternatively, "
"one can use synth_mcx_1_clean to construct the gate directly."
),
since="1.3",
pending=True,
)
def __init__(
self,
num_ctrl_qubits: int,
Expand Down Expand Up @@ -1409,6 +1426,18 @@ def __new__(
unit=unit,
)

@deprecate_func(
additional_msg=(
"It is recommended to use MCXGate and let HighLevelSynthesis choose "
"the best synthesis method depending on the number of ancilla qubits available. "
"If this specific synthesis method is required, one can specify it using the "
"high-level-synthesis plugins `n_clean_m15` (using clean ancillas) or "
"`n_dirty_i15` (using dirty ancillas) for MCX gates. Alternatively, one can "
"use synth_mcx_n_dirty_i15 and synth_mcx_n_clean_m15 to construct the gate directly."
),
since="1.3",
pending=True,
)
def __init__(
self,
num_ctrl_qubits: int,
Expand Down
9 changes: 8 additions & 1 deletion qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3212,10 +3212,17 @@ def decompose(
from qiskit.converters.dag_to_circuit import dag_to_circuit

dag = circuit_to_dag(self, copy_operations=True)
dag = HighLevelSynthesis().run(dag)

if gates_to_decompose is None:
# We should not rewrite the circuit using HLS when we have gates_to_decompose,
# or else HLS will rewrite all objects with available plugins (e.g., Cliffords,
# PermutationGates, and now also MCXGates)
dag = HighLevelSynthesis().run(dag)

pass_ = Decompose(gates_to_decompose)
for _ in range(reps):
dag = pass_.run(dag)

# do not copy operations, this is done in the conversion with circuit_to_dag
return dag_to_circuit(dag, copy_operations=False)

Expand Down
10 changes: 9 additions & 1 deletion qiskit/synthesis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@
.. autofunction:: synth_mcx_n_dirty_i15
.. autofunction:: synth_mcx_n_clean_m15
.. autofunction:: synth_mcx_1_clean_b95
.. autofunction:: synth_mcx_noaux_v24
.. autofunction:: synth_mcx_gray_code
.. autofunction:: synth_c3x
.. autofunction:: synth_c4x
"""

Expand Down Expand Up @@ -180,8 +184,12 @@
two_qubit_cnot_decompose,
TwoQubitWeylDecomposition,
)
from .multi_controlled.mcx_with_ancillas_synth import (
from .multi_controlled import (
synth_mcx_n_dirty_i15,
synth_mcx_n_clean_m15,
synth_mcx_1_clean_b95,
synth_mcx_noaux_v24,
synth_mcx_gray_code,
synth_c3x,
synth_c4x,
)
6 changes: 5 additions & 1 deletion qiskit/synthesis/multi_controlled/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@

"""Module containing multi-controlled circuits synthesis"""

from .mcx_with_ancillas_synth import (
from .mcx_synthesis import (
synth_mcx_n_dirty_i15,
synth_mcx_n_clean_m15,
synth_mcx_1_clean_b95,
synth_mcx_gray_code,
synth_mcx_noaux_v24,
synth_c3x,
synth_c4x,
)
Loading

0 comments on commit 072548f

Please sign in to comment.