diff --git a/qiskit/transpiler/passes/utils/gate_direction.py b/qiskit/transpiler/passes/utils/gate_direction.py index 323f19bf7fe9..262538a2fad0 100644 --- a/qiskit/transpiler/passes/utils/gate_direction.py +++ b/qiskit/transpiler/passes/utils/gate_direction.py @@ -19,7 +19,7 @@ from qiskit.converters import dag_to_circuit, circuit_to_dag from qiskit.circuit import QuantumRegister, ControlFlowOp -from qiskit.dagcircuit import DAGCircuit +from qiskit.dagcircuit import DAGCircuit, DAGOpNode from qiskit.circuit.library.standard_gates import ( RYGate, HGate, @@ -34,6 +34,10 @@ ) +def _swap_node_qargs(node): + return DAGOpNode(node.op, node.qargs[::-1], node.cargs) + + class GateDirection(TransformationPass): """Modify asymmetric gates to match the hardware coupling direction. @@ -58,6 +62,8 @@ class GateDirection(TransformationPass): └──────┘ └───┘└──────┘└───┘ """ + _KNOWN_REPLACEMENTS = frozenset(["cx", "cz", "ecr", "swap", "rzx", "rxx", "ryy", "rzz"]) + def __init__(self, coupling_map, target=None): """GateDirection pass. @@ -99,6 +105,8 @@ def __init__(self, coupling_map, target=None): self._swap_dag.add_qreg(qr) self._swap_dag.apply_operation_back(SwapGate(), [qr[1], qr[0]], []) + # If adding more replacements (either static or dynamic), also update the class variable + # `_KNOWN_REPLACMENTS` to include them in the error messages. self._static_replacements = { "cx": self._cx_dag, "cz": self._cz_dag, @@ -187,8 +195,9 @@ def _run_coupling_map(self, dag, wire_map, edges=None): dag.substitute_node_with_dag(node, self._rzz_dag(*node.op.params)) else: raise TranspilerError( - f"Flipping of gate direction is only supported " - f"for {list(self._static_replacements)} at this time, not '{node.name}'." + f"'{node.name}' would be supported on '{qargs}' if the direction were" + f" swapped, but no rules are known to do that." + f" {list(self._KNOWN_REPLACEMENTS)} can be automatically flipped." ) return dag @@ -281,11 +290,22 @@ def _run_target(self, dag, wire_map): f"The circuit requires a connection between physical qubits {qargs}" f" for {node.name}" ) + elif self.target.instruction_supported(node.name, qargs): + continue + elif self.target.instruction_supported(node.name, swapped) or dag.has_calibration_for( + _swap_node_qargs(node) + ): + raise TranspilerError( + f"'{node.name}' would be supported on '{qargs}' if the direction were" + f" swapped, but no rules are known to do that." + f" {list(self._KNOWN_REPLACEMENTS)} can be automatically flipped." + ) else: raise TranspilerError( - f"Flipping of gate direction is only supported " - f"for {list(self._static_replacements)} at this time, not '{node.name}'." + f"'{node.name}' with parameters '{node.op.params}' is not supported on qubits" + f" '{qargs}' in either direction." ) + return dag def run(self, dag): diff --git a/test/python/transpiler/test_gate_direction.py b/test/python/transpiler/test_gate_direction.py index 06c1d814b147..a45568efdf58 100644 --- a/test/python/transpiler/test_gate_direction.py +++ b/test/python/transpiler/test_gate_direction.py @@ -439,6 +439,49 @@ def test_target_control_flow(self): pass_ = GateDirection(None, target) self.assertEqual(pass_(circuit), expected) + def test_target_cannot_flip_message(self): + """A suitable error message should be emitted if the gate would be supported if it were + flipped.""" + gate = Gate("my_2q_gate", 2, []) + target = Target(num_qubits=2) + target.add_instruction(gate, properties={(0, 1): None}) + + circuit = QuantumCircuit(2) + circuit.append(gate, (1, 0)) + + pass_ = GateDirection(None, target) + with self.assertRaisesRegex(TranspilerError, "'my_2q_gate' would be supported.*"): + pass_(circuit) + + def test_target_cannot_flip_message_calibrated(self): + """A suitable error message should be emitted if the gate would be supported if it were + flipped.""" + target = Target(num_qubits=2) + target.add_instruction(CXGate(), properties={(0, 1): None}) + + gate = Gate("my_2q_gate", 2, []) + circuit = QuantumCircuit(2) + circuit.append(gate, (1, 0)) + circuit.add_calibration(gate, (0, 1), pulse.ScheduleBlock()) + + pass_ = GateDirection(None, target) + with self.assertRaisesRegex(TranspilerError, "'my_2q_gate' would be supported.*"): + pass_(circuit) + + def test_target_unknown_gate_message(self): + """A suitable error message should be emitted if the gate isn't valid in either direction on + the target.""" + gate = Gate("my_2q_gate", 2, []) + target = Target(num_qubits=2) + target.add_instruction(CXGate(), properties={(0, 1): None}) + + circuit = QuantumCircuit(2) + circuit.append(gate, (0, 1)) + + pass_ = GateDirection(None, target) + with self.assertRaisesRegex(TranspilerError, "'my_2q_gate'.*not supported on qubits .*"): + pass_(circuit) + def test_allows_calibrated_gates_coupling_map(self): """Test that the gate direction pass allows a gate that's got a calibration to pass through without error."""