Skip to content

Commit

Permalink
Improve error messages in Target-based GateDirection pass (#9787)
Browse files Browse the repository at this point in the history
* Improve error messages in `Target`-based `GateDirection` pass

The `Target` version of `GateDirection` has more information available
to make better error messages. It can distinguish cases where the
failure is because a gate would be valid if flipped but the pass doesn't
know how to do it, and the case where the gate wouldn't be valid in
either direction.

* Reword error message on failed flip

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
(cherry picked from commit 648da26)

# Conflicts:
#	qiskit/transpiler/passes/utils/gate_direction.py
  • Loading branch information
jakelishman authored and mergify[bot] committed Mar 14, 2023
1 parent 44cda51 commit e3dc2bc
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 5 deletions.
44 changes: 39 additions & 5 deletions qiskit/transpiler/passes/utils/gate_direction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -33,6 +33,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.
Expand All @@ -57,6 +61,8 @@ class GateDirection(TransformationPass):
└──────┘ └───┘└──────┘└───┘
"""

_KNOWN_REPLACEMENTS = frozenset(["cx", "cz", "ecr", "swap", "rzx", "rxx", "ryy", "rzz"])

def __init__(self, coupling_map, target=None):
"""GateDirection pass.
Expand Down Expand Up @@ -93,7 +99,23 @@ def __init__(self, coupling_map, target=None):
self._cz_dag.add_qreg(qr)
self._cz_dag.apply_operation_back(CZGate(), [qr[1], qr[0]], [])

<<<<<<< HEAD
self._static_replacements = {"cx": self._cx_dag, "cz": self._cz_dag, "ecr": self._ecr_dag}
=======
self._swap_dag = DAGCircuit()
qr = QuantumRegister(2)
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,
"ecr": self._ecr_dag,
"swap": self._swap_dag,
}
>>>>>>> 648da26c6 (Improve error messages in `Target`-based `GateDirection` pass (#9787))

@staticmethod
def _rzx_dag(parameter):
Expand Down Expand Up @@ -176,8 +198,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

Expand Down Expand Up @@ -270,11 +293,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):
Expand Down
43 changes: 43 additions & 0 deletions test/python/transpiler/test_gate_direction.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,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."""
Expand Down

0 comments on commit e3dc2bc

Please sign in to comment.