From 5b5b37617cb79b383c7007d9c674b11a15c54f83 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 30 Sep 2021 18:39:29 -0400 Subject: [PATCH] Add min_qubits kwarg to UnitarySynthesis pass (#6349) * Rely on UnitarySynthesis pass for unrolling UnitaryGates This commit adds the UnitarySynthesis pass to the preset pass manager anywhere unrolling of custom or wide gates was done. Previously, we only ever called the UnitarySynthesis pass in the preset pass managers if the basis translation method was set to 'synthesis' or we were using level3 and then it was part of the optimization pass (as part of 2q peephole optimization). This was an issue in that we're implicitly calling the _define() method of the class whenever we're unrolling gates >= 3q or gates with a custom definition. The _define() method is basically identical to the UnitarySynthesis pass except it doesn't expose the options to set a basis gate or approximation degree, which would result in the output gates from unitary gates in the unroll steps are always in the U3 basis. This meant we would be converting unitary gates to u3 (and cx) which would result in a conversion to the target basis later, even if we could just go to the target basis directly. This is also future proofing for #6124 where a plugin interface is added to the UnitarySynthesis pass and can potentially be used for arbitrary sized unitaries. At the same time this change caught an issue qith the SingleQubitUnitary gate where the name was duplicated with the UnitaryGate which would result in errors when UnitarySynthesis was called because the UnitarySynthesis pass looks for gate named 'unitary' to run on. This is fixed and the SingleQubitUnitary gate's name is changed to 'squ' to differentiate it from the UnitaryGate. * Add option to set a minimum size to unitarysynthesis pass This commit adds a new option to the UnitarySynthesis pass constructor, min_qubits, which is used to specify a minimimum size unitary to synthesize. If the unitary is smaller than that it will be skipped. This is then used by the UnitarySynthesis instance in the unroll3q phase so we don't decompose 1 or 2q unitaries before routing. * Fix rebase error * Correct oversights from rebase Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/extensions/quantum_initializer/squ.py | 2 +- .../passes/synthesis/unitary_synthesis.py | 8 +++++- .../transpiler/preset_passmanagers/level0.py | 22 +++++++++++++++- .../transpiler/preset_passmanagers/level1.py | 26 ++++++++++++++++++- .../transpiler/preset_passmanagers/level2.py | 26 ++++++++++++++++++- .../transpiler/preset_passmanagers/level3.py | 22 +++++++++++++++- .../notes/squ-gate-name-785b7896300a92ef.yaml | 18 +++++++++++++ 7 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/squ-gate-name-785b7896300a92ef.yaml diff --git a/qiskit/extensions/quantum_initializer/squ.py b/qiskit/extensions/quantum_initializer/squ.py index 9f3861ebe23e..285ae1b8f22c 100644 --- a/qiskit/extensions/quantum_initializer/squ.py +++ b/qiskit/extensions/quantum_initializer/squ.py @@ -59,7 +59,7 @@ def __init__(self, unitary_matrix, mode="ZYZ", up_to_diagonal=False, u=None): self._diag = None # Create new gate - super().__init__("unitary", 1, [unitary_matrix]) + super().__init__("squ", 1, [unitary_matrix]) def inverse(self): """Return the inverse. diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 2f8739488e10..b4d38d9e3d07 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -91,6 +91,7 @@ def __init__( natural_direction: Union[bool, None] = None, synth_gates: Union[List[str], None] = None, method: str = "default", + min_qubits: int = None, ): """Synthesize unitaries over some basis gates. @@ -131,11 +132,14 @@ def __init__( ['unitary']. If None and `pulse_optimzie` == True, default to ['unitary', 'swap'] method (str): The unitary synthesis method plugin to use. - + min_qubits: The minimum number of qubits in the unitary to synthesize. If this is set + and the unitary is less than the specified number of qubits it will not be + synthesized. """ super().__init__() self._basis_gates = basis_gates self._approximation_degree = approximation_degree + self._min_qubits = min_qubits self.method = method self.plugins = plugin.UnitarySynthesisPluginManager() self._coupling_map = coupling_map @@ -194,6 +198,8 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: plugin_method._approximation_degree = self._approximation_degree for node in dag.named_nodes(*self._synth_gates): + if self._min_qubits is not None and len(node.qargs) < self._min_qubits: + continue if plugin_method.supports_coupling_map: kwargs["coupling_map"] = ( self._coupling_map, diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index f598f5d5d8dc..070eb8740031 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -113,12 +113,14 @@ def _choose_layout_condition(property_set): # 3. Decompose so only 1-qubit and 2-qubit gates remain _unroll3q = [ + # Use unitary synthesis for basis aware decomposition of UnitaryGates UnitarySynthesis( basis_gates, approximation_degree=approximation_degree, coupling_map=coupling_map, backend_props=backend_properties, method=unitary_synthesis_method, + min_qubits=3, ), Unroll3qOrMore(), ] @@ -155,9 +157,27 @@ def _swap_condition(property_set): elif translation_method == "translator": from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - _unroll = [UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates)] + _unroll = [ + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_properties, + method=unitary_synthesis_method, + ), + UnrollCustomDefinitions(sel, basis_gates), + BasisTranslator(sel, basis_gates), + ] elif translation_method == "synthesis": _unroll = [ + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_properties, + method=unitary_synthesis_method, + min_qubits=3, + ), Unroll3qOrMore(), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 0cff8a5f5dbd..6483eebffb03 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -133,12 +133,14 @@ def _not_perfect_yet(property_set): # 4. Decompose so only 1-qubit and 2-qubit gates remain _unroll3q = [ + # Use unitary synthesis for basis aware decomposition of UnitaryGates UnitarySynthesis( basis_gates, approximation_degree=approximation_degree, coupling_map=coupling_map, method=unitary_synthesis_method, backend_props=backend_properties, + min_qubits=3, ), Unroll3qOrMore(), ] @@ -175,9 +177,31 @@ def _swap_condition(property_set): elif translation_method == "translator": from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - _unroll = [UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates)] + _unroll = [ + # Use unitary synthesis for basis aware decomposition of UnitaryGates before + # custom unrolling + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + method=unitary_synthesis_method, + backend_props=backend_properties, + ), + UnrollCustomDefinitions(sel, basis_gates), + BasisTranslator(sel, basis_gates), + ] elif translation_method == "synthesis": _unroll = [ + # Use unitary synthesis for basis aware decomposition of UnitaryGates before + # collection + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + method=unitary_synthesis_method, + backend_props=backend_properties, + min_qubits=3, + ), Unroll3qOrMore(), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 929e9b270c45..4daf5b1c1d1a 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -167,12 +167,14 @@ def _csp_not_found_match(property_set): # 3. Unroll to 1q or 2q gates _unroll3q = [ + # Use unitary synthesis for basis aware decomposition of UnitaryGates UnitarySynthesis( basis_gates, approximation_degree=approximation_degree, coupling_map=coupling_map, backend_props=backend_properties, method=unitary_synthesis_method, + min_qubits=3, ), Unroll3qOrMore(), ] @@ -209,9 +211,31 @@ def _swap_condition(property_set): elif translation_method == "translator": from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - _unroll = [UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates)] + _unroll = [ + # Use unitary synthesis for basis aware decomposition of UnitaryGates before + # custom unrolling + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_properties, + method=unitary_synthesis_method, + ), + UnrollCustomDefinitions(sel, basis_gates), + BasisTranslator(sel, basis_gates), + ] elif translation_method == "synthesis": _unroll = [ + # Use unitary synthesis for basis aware decomposition of UnitaryGates before + # collection + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_properties, + method=unitary_synthesis_method, + min_qubits=3, + ), Unroll3qOrMore(), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 8f6cecb7be62..1d0c56264239 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -108,12 +108,14 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: # 1. Unroll to 1q or 2q gates _unroll3q = [ + # Use unitary synthesis for basis aware decomposition of UnitaryGates UnitarySynthesis( basis_gates, approximation_degree=approximation_degree, coupling_map=coupling_map, backend_props=backend_properties, method=unitary_synthesis_method, + min_qubits=3, ), Unroll3qOrMore(), ] @@ -212,9 +214,27 @@ def _swap_condition(property_set): elif translation_method == "translator": from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - _unroll = [UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates)] + _unroll = [ + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_properties, + method=unitary_synthesis_method, + ), + UnrollCustomDefinitions(sel, basis_gates), + BasisTranslator(sel, basis_gates), + ] elif translation_method == "synthesis": _unroll = [ + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_properties, + method=unitary_synthesis_method, + min_qubits=3, + ), Unroll3qOrMore(), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), diff --git a/releasenotes/notes/squ-gate-name-785b7896300a92ef.yaml b/releasenotes/notes/squ-gate-name-785b7896300a92ef.yaml new file mode 100644 index 000000000000..2b0e6fda182a --- /dev/null +++ b/releasenotes/notes/squ-gate-name-785b7896300a92ef.yaml @@ -0,0 +1,18 @@ +--- +features: + - | + The :class:`~qiskit.transpiler.passes.UnitarySynthesis` transpiler pass in + :mod:`qiskit.transpiler.passes` has a new kwarg in the constructor, + ``min_qubits``. When specified this can be set to an ``int`` value which + is the minimum size :class:`~qiskit.extensions.UnitaryGate` object to + run the unitary synthesis on. If a :class:`~qiskit.extensions.UnitaryGate` + in a :class:`~qiskit.circuit.QuantumCircuit` uses fewer qubits it will + be skipped by that instance of the pass. +upgrade: + - | + The :attr:`~qiskit.extensions.SingleQubitUnitary.name` attribute of the + :class:`~qiskit.extensions.SingleQubitUnitary` gate class has been changed + from ``unitary`` to ``squ``. This was necessary to avoid a conflict with + the :class:`~qiskit.extensions.UnitaryGate` class's name which was also + ``unitary`` since the 2 gates are not the same and don't have the same + implementation (and can't be used interchangeably).