From 49fcc41b4e618aae9ec3155ada18bfeca298598d Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 5 Oct 2021 15:46:44 -0400 Subject: [PATCH] Ensure UnitaryGate's are preserved by transpile if in basis gates This commit tweaks the preset passmanager definitions to not include the UnitarySynthesis pass as part of basis translation if the 'unitary' gate is in the basis gates. Previously in #6124 and #6349 the preset passmanagers were updated to leverage the user specified unitary synthesis method instead of implicitly using the default as part of the unroll 3q and unroll custom definition passes. This however had the unintended side effect of always synthesizing unitary gates even if it was in the basis (which is the case on Aer). This commit fixes this by only running unitary synthesis as part of the basis translation step if it's not in the basis. A potential follow-on here is to make the unroll 3q transpiler pass basis aware (since right now it will implicitly run unitary synthesis internally) and add a similar logic around the use of the UnitarySynthesis pass for unrolling 3q or larger gates. The unroll 3q transpiler stage suffers from this same issue. However, this wasn't done here because the way 3q unrolling is used is to reduce the gates to be less than 3 qubits so the layout and routing phase can deal work with the gates in the circuit would likely cause issues if a unitary gate larger than 2 qubits was in the circuit being transpiled. --- .../transpiler/preset_passmanagers/level0.py | 21 ++++++++++------ .../transpiler/preset_passmanagers/level1.py | 21 ++++++++++------ .../transpiler/preset_passmanagers/level2.py | 25 +++++++++++-------- .../transpiler/preset_passmanagers/level3.py | 21 ++++++++++------ .../transpiler/test_preset_passmanagers.py | 10 ++++++++ 5 files changed, 64 insertions(+), 34 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 070eb8740031..78ce3dc7b117 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -157,14 +157,19 @@ def _swap_condition(property_set): elif translation_method == "translator": from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - _unroll = [ - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_properties, - method=unitary_synthesis_method, - ), + if basis_gates is not None and "unitary" not in basis_gates: + _unroll = [ + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_properties, + method=unitary_synthesis_method, + ) + ] + else: + _unroll = [] + _unroll += [ UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates), ] diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 6483eebffb03..af2bfa808a4d 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -177,16 +177,21 @@ def _swap_condition(property_set): elif translation_method == "translator": from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - _unroll = [ + if basis_gates is not None and "unitary" not in basis_gates: # 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, - ), + _unroll = [ + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_properties, + method=unitary_synthesis_method, + ) + ] + else: + _unroll = [] + _unroll += [ UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates), ] diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 4daf5b1c1d1a..efb991747100 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -211,16 +211,21 @@ def _swap_condition(property_set): elif translation_method == "translator": from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - _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, - ), + # Use unitary synthesis for basis aware decomposition of UnitaryGates before + # custom unrolling + if basis_gates is not None and "unitary" not in basis_gates: + _unroll = [ + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_properties, + method=unitary_synthesis_method, + ) + ] + else: + _unroll = [] + _unroll += [ UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates), ] diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 1d0c56264239..15b101cefb65 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -214,14 +214,19 @@ def _swap_condition(property_set): elif translation_method == "translator": from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - _unroll = [ - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_properties, - method=unitary_synthesis_method, - ), + if basis_gates is not None and "unitary" not in basis_gates: + _unroll = [ + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_properties, + method=unitary_synthesis_method, + ) + ] + else: + _unroll = [] + _unroll += [ UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates), ] diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 6a16d2f25766..3e001c7456ba 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -32,6 +32,7 @@ ) from qiskit.converters import circuit_to_dag from qiskit.circuit.library import GraphState +from qiskit.quantum_info import random_unitary def emptycircuit(): @@ -98,6 +99,15 @@ def test_level0_keeps_reset(self): result = transpile(circuit, basis_gates=None, optimization_level=0) self.assertEqual(result, circuit) + @combine(level=[0, 1, 2, 3], name="level{level}") + def test_unitary_is_preserved_if_in_basis(self, level): + """Test that a unitary is not synthesized if in the basis.""" + qc = QuantumCircuit(2) + qc.unitary(random_unitary(4, seed=42), [0, 1]) + qc.measure_all() + result = transpile(qc, basis_gates=["cx", "u", "unitary"], optimization_level=level) + self.assertEqual(result, qc) + @combine(level=[0, 1, 2, 3], name="level{level}") def test_respect_basis(self, level): """Test that all levels respect basis"""