From a43c61d39117e607c539bf9de927d14b3600f474 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 14 Mar 2023 10:18:45 -0400 Subject: [PATCH] Fix basis_gates and coupling_map backend override in transpile() (#9789) This commit fixes an issue in the transpile() function when a user specified the `backend` argument with a BackendV2 based backend along with `basis_gates` or `coupling_map`. In this case the `transpile()` was generating the preset pass manager with a target, coupling map, and basis gates list. However, most individual passes that take basis gates and a Target will prefer to use the target if both are specified. This is generally sane behavior at the pass level because the target contains more rich data and tighter constraints that a transpiler pass will need to worry about. To fix this limitation this commit updates transpile() to not use the backend's target if either basis_gates or coupling_map are specified. Longer term this should no longer be an issue when #9256 is implemented and we'll be relying solely on a target internally. But this fix is needed until #9256 is started. Fixes #9781 Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> (cherry picked from commit bf3bb96cebc141f79ce46b5938724eef30778c8e) --- qiskit/compiler/transpiler.py | 6 ++- test/python/compiler/test_transpiler.py | 72 +++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 4d82214a5e03..d8a3be860321 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -645,6 +645,11 @@ def _parse_transpile_args( timing_constraints = target.timing_constraints() if backend_properties is None: backend_properties = target_to_backend_properties(target) + # If target is not specified and any hardware constraint object is + # manually specified then do not use the target from the backend as + # it is invalidated by a custom basis gate list or a custom coupling map + elif basis_gates is None and coupling_map is None: + target = _parse_target(backend, target) basis_gates = _parse_basis_gates(basis_gates, backend) initial_layout = _parse_initial_layout(initial_layout, circuits) @@ -658,7 +663,6 @@ def _parse_transpile_args( callback = _parse_callback(callback, num_circuits) durations = _parse_instruction_durations(backend, instruction_durations, dt, circuits) timing_constraints = _parse_timing_constraints(backend, timing_constraints, num_circuits) - target = _parse_target(backend, target) if scheduling_method and any(d is None for d in durations): raise TranspilerError( "Transpiling a circuit with a scheduling method" diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 8ede5702ae60..400662f30256 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -51,6 +51,7 @@ FakeRueschlikon, FakeBoeblingen, FakeMumbaiV2, + FakeNairobiV2, ) from qiskit.transpiler import Layout, CouplingMap from qiskit.transpiler import PassManager, TransformationPass @@ -61,6 +62,7 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.preset_passmanagers import level_0_pass_manager from qiskit.tools import parallel +from qiskit.pulse import InstructionScheduleMap class CustomCX(Gate): @@ -1899,3 +1901,73 @@ def run(self, dag): for qc_test in qcs_cal_added: added_cal = qc_test.calibrations["sx"][((0,), tuple())] self.assertEqual(added_cal, ref_cal) + + @data(0, 1, 2, 3) + def test_backendv2_and_basis_gates(self, opt_level): + """Test transpile() with BackendV2 and basis_gates set.""" + backend = FakeNairobiV2() + qc = QuantumCircuit(5) + qc.h(0) + qc.cz(0, 1) + qc.cz(0, 2) + qc.cz(0, 3) + qc.cz(0, 4) + qc.measure_all() + tqc = transpile( + qc, + backend=backend, + basis_gates=["u", "cz"], + optimization_level=opt_level, + seed_transpiler=12345678942, + ) + op_count = set(tqc.count_ops()) + self.assertEqual({"u", "cz", "measure", "barrier"}, op_count) + for inst in tqc.data: + if inst.operation.name not in {"u", "cz"}: + continue + qubits = tuple(tqc.find_bit(x).index for x in inst.qubits) + self.assertIn(qubits, backend.target.qargs) + + @data(0, 1, 2, 3) + def test_backendv2_and_coupling_map(self, opt_level): + """Test transpile() with custom coupling map.""" + backend = FakeNairobiV2() + qc = QuantumCircuit(5) + qc.h(0) + qc.cz(0, 1) + qc.cz(0, 2) + qc.cz(0, 3) + qc.cz(0, 4) + qc.measure_all() + cmap = CouplingMap.from_line(5, bidirectional=False) + tqc = transpile( + qc, + backend=backend, + coupling_map=cmap, + optimization_level=opt_level, + seed_transpiler=12345678942, + ) + op_count = set(tqc.count_ops()) + self.assertTrue({"rz", "sx", "x", "cx", "measure", "barrier"}.issuperset(op_count)) + for inst in tqc.data: + if len(inst.qubits) == 2: + qubit_0 = tqc.find_bit(inst.qubits[0]).index + qubit_1 = tqc.find_bit(inst.qubits[1]).index + self.assertEqual(qubit_1, qubit_0 + 1) + + @data(0, 1, 2, 3) + def test_backend_and_custom_gate(self, opt_level): + """Test transpile() with BackendV2, custom basis pulse gate.""" + backend = FakeNairobiV2() + inst_map = InstructionScheduleMap() + inst_map.add("newgate", [0, 1], pulse.ScheduleBlock()) + newgate = Gate("newgate", 2, []) + circ = QuantumCircuit(2) + circ.append(newgate, [0, 1]) + tqc = transpile( + circ, backend, inst_map=inst_map, basis_gates=["newgate"], optimization_level=opt_level + ) + self.assertEqual(len(tqc.data), 1) + self.assertEqual(tqc.data[0].operation, newgate) + qubits = tuple(tqc.find_bit(x).index for x in tqc.data[0].qubits) + self.assertIn(qubits, backend.target.qargs)