From eaf8dca55aed9244d21c951554886eb7af739de2 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 13 Mar 2023 15:36:07 -0400 Subject: [PATCH] Fix basis_gates and coupling_map backend override in transpile() 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 --- 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 eca13cde7bd1..b0369d9561f0 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 f347168e56ea..15cec11eeb5a 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,), ())] 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)