From a501fce397b920ebe14b8eff1a12a8b63fd9fa14 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 4 May 2021 09:44:50 -0400 Subject: [PATCH 1/4] 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. --- qiskit/extensions/quantum_initializer/squ.py | 2 +- qiskit/transpiler/preset_passmanagers/level0.py | 13 +++++++++++-- qiskit/transpiler/preset_passmanagers/level1.py | 17 +++++++++++++++-- qiskit/transpiler/preset_passmanagers/level2.py | 17 +++++++++++++++-- qiskit/transpiler/preset_passmanagers/level3.py | 6 +++++- .../notes/squ-gate-name-785b7896300a92ef.yaml | 9 +++++++++ 6 files changed, 56 insertions(+), 8 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 0136898009f3..eb1c248e39de 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/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index e34cb4cd92a2..c306247022f1 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -105,7 +105,11 @@ def _choose_layout_condition(property_set): _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] # 3. Decompose so only 1-qubit and 2-qubit gates remain - _unroll3q = Unroll3qOrMore() + _unroll3q = [ + # Use unitary synthesis for basis aware decomposition of UnitaryGates + UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), + Unroll3qOrMore(), + ] # 4. Swap to fit the coupling map _swap_check = CheckMap(coupling_map) @@ -139,9 +143,14 @@ 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), + UnrollCustomDefinitions(sel, basis_gates), + BasisTranslator(sel, basis_gates), + ] elif translation_method == "synthesis": _unroll = [ + UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), Unroll3qOrMore(), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index deab87f30467..05ea8c1225bc 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -125,7 +125,11 @@ def _not_perfect_yet(property_set): _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] # 4. Decompose so only 1-qubit and 2-qubit gates remain - _unroll3q = Unroll3qOrMore() + _unroll3q = [ + # Use unitary synthesis for basis aware decomposition of UnitaryGates + UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), + Unroll3qOrMore(), + ] # 5. Swap to fit the coupling map _swap_check = CheckMap(coupling_map) @@ -159,9 +163,18 @@ 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), + 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), Unroll3qOrMore(), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index d26222b3cc61..6778e49d54bb 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -159,7 +159,11 @@ def _csp_not_found_match(property_set): _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] # 3. Unroll to 1q or 2q gates - _unroll3q = Unroll3qOrMore() + _unroll3q = [ + # Use unitary synthesis for basis aware decomposition of UnitaryGates + UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), + Unroll3qOrMore(), + ] # 4. Swap to fit the coupling map _swap_check = CheckMap(coupling_map) @@ -193,9 +197,18 @@ 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), + 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), Unroll3qOrMore(), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 257bc4babe88..ffe12c5918f4 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -100,7 +100,11 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: approximation_degree = pass_manager_config.approximation_degree # 1. Unroll to 1q or 2q gates - _unroll3q = Unroll3qOrMore() + _unroll3q = [ + # Use unitary synthesis for basis aware decomposition of UnitaryGates + UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), + Unroll3qOrMore(), + ] # 2. Layout on good qubits if calibration info available, otherwise on dense links _given_layout = SetLayout(initial_layout) diff --git a/releasenotes/notes/squ-gate-name-785b7896300a92ef.yaml b/releasenotes/notes/squ-gate-name-785b7896300a92ef.yaml new file mode 100644 index 000000000000..36608e046781 --- /dev/null +++ b/releasenotes/notes/squ-gate-name-785b7896300a92ef.yaml @@ -0,0 +1,9 @@ +--- +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). From 401aedfb08d5aa469887e4d3c68ad09a502ee34f Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 19 May 2021 12:17:30 -0400 Subject: [PATCH 2/4] 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. --- .../transpiler/passes/synthesis/unitary_synthesis.py | 10 +++++++++- qiskit/transpiler/preset_passmanagers/level0.py | 2 +- qiskit/transpiler/preset_passmanagers/level1.py | 2 +- qiskit/transpiler/preset_passmanagers/level2.py | 2 +- qiskit/transpiler/preset_passmanagers/level3.py | 2 +- releasenotes/notes/squ-gate-name-785b7896300a92ef.yaml | 9 +++++++++ 6 files changed, 22 insertions(+), 5 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 9d0baa973ecc..2b57aac8a32e 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -57,7 +57,9 @@ def _choose_euler_basis(basis_gates): class UnitarySynthesis(TransformationPass): """Synthesize gates according to their basis gates.""" - def __init__(self, basis_gates: List[str], approximation_degree: float = 1): + def __init__( + self, basis_gates: List[str], approximation_degree: float = 1, min_qubits: int = None + ): """ Synthesize unitaries over some basis gates. @@ -68,10 +70,14 @@ def __init__(self, basis_gates: List[str], approximation_degree: float = 1): Args: basis_gates: List of gate names to target. approximation_degree: closeness of approximation (0: lowest, 1: highest). + 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 def run(self, dag: DAGCircuit) -> DAGCircuit: """Run the UnitarySynthesis pass on `dag`. @@ -92,6 +98,8 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: decomposer2q = TwoQubitBasisDecomposer(kak_gate, euler_basis=euler_basis) for node in dag.named_nodes("unitary"): + if self._min_qubits is not None and len(node.qargs) < self._min_qubits: + continue synth_dag = None if len(node.qargs) == 1: diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index c306247022f1..740ec34a4171 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -107,7 +107,7 @@ 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), + UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, min_qubits=3), Unroll3qOrMore(), ] diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 05ea8c1225bc..e24ac4ab1a2a 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -127,7 +127,7 @@ 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), + UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, min_qubits=3), Unroll3qOrMore(), ] diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 6778e49d54bb..956736161011 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -161,7 +161,7 @@ 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), + UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, min_qubits=3), Unroll3qOrMore(), ] diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index ffe12c5918f4..d172cdc40fe4 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -102,7 +102,7 @@ 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), + UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, min_qubits=3), Unroll3qOrMore(), ] diff --git a/releasenotes/notes/squ-gate-name-785b7896300a92ef.yaml b/releasenotes/notes/squ-gate-name-785b7896300a92ef.yaml index 36608e046781..2b0e6fda182a 100644 --- a/releasenotes/notes/squ-gate-name-785b7896300a92ef.yaml +++ b/releasenotes/notes/squ-gate-name-785b7896300a92ef.yaml @@ -1,4 +1,13 @@ --- +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 From 657d09616ce97ee1eb3c4ed8e1c1c13a2d8df8fc Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 28 Jul 2021 17:38:45 -0400 Subject: [PATCH 3/4] Fix rebase error --- qiskit/transpiler/passes/synthesis/unitary_synthesis.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 58e5517b0501..ade904fe831c 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -63,7 +63,6 @@ class UnitarySynthesis(TransformationPass): """Synthesize gates according to their basis gates.""" def __init__( - self, basis_gates: List[str], approximation_degree: float = 1, self, basis_gates: List[str], approximation_degree: float = 1, @@ -72,7 +71,7 @@ def __init__( pulse_optimize: Union[bool, None] = None, natural_direction: Union[bool, None] = None, synth_gates: Union[List[str], None] = None, - min_qubits: int = None + min_qubits: int = None, ): """Synthesize unitaries over some basis gates. @@ -163,7 +162,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: 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 self._basis_gates and node.name in self._basis_gates: continue synth_dag = None From 7f4c08894f556ed3bf40b6249488a743fca9bd74 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 30 Sep 2021 15:57:05 -0400 Subject: [PATCH 4/4] Correct oversights from rebase --- .../transpiler/preset_passmanagers/level0.py | 17 ++++++++++++++-- .../transpiler/preset_passmanagers/level1.py | 17 ++++++++++++++-- .../transpiler/preset_passmanagers/level2.py | 17 ++++++++++++++-- .../transpiler/preset_passmanagers/level3.py | 20 ++++++++++++++++++- 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 588af3dedc9b..070eb8740031 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -158,13 +158,26 @@ def _swap_condition(property_set): from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel _unroll = [ - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), + 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), + 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 60b87da66e2b..6483eebffb03 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -180,7 +180,13 @@ def _swap_condition(property_set): _unroll = [ # Use unitary synthesis for basis aware decomposition of UnitaryGates before # custom unrolling - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), + 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), ] @@ -188,7 +194,14 @@ def _swap_condition(property_set): _unroll = [ # Use unitary synthesis for basis aware decomposition of UnitaryGates before # collection - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), + 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 c854be1b7b4a..4daf5b1c1d1a 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -214,7 +214,13 @@ def _swap_condition(property_set): _unroll = [ # Use unitary synthesis for basis aware decomposition of UnitaryGates before # custom unrolling - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), + 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), ] @@ -222,7 +228,14 @@ def _swap_condition(property_set): _unroll = [ # Use unitary synthesis for basis aware decomposition of UnitaryGates before # collection - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), + 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 cbe16ac4c55b..1d0c56264239 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -214,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),