Skip to content

Commit

Permalink
Make UnrollCustomDefinitions pass target aware
Browse files Browse the repository at this point in the history
This commit makes the UnrollCustomDefinitions transpiler pass target
aware. A new kwarg, target is added to the pass constructor and if
specified the Target object passed in is used to check if the target
backend supports the gates in the circuit instead of the basis_gates
list.

Part of Qiskit#7113
  • Loading branch information
mtreinish committed Sep 23, 2022
1 parent b7d5f4a commit ea51a2e
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 15 deletions.
41 changes: 29 additions & 12 deletions qiskit/transpiler/passes/basis/unroll_custom_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,23 @@
class UnrollCustomDefinitions(TransformationPass):
"""Unrolls instructions with custom definitions."""

def __init__(self, equivalence_library, basis_gates):
def __init__(self, equivalence_library, basis_gates=None, target=None):
"""Unrolls instructions with custom definitions.
Args:
equivalence_library (EquivalenceLibrary): The equivalence library
which will be used by the BasisTranslator pass. (Instructions in
this library will not be unrolled by this pass.)
basis_gates (list[str]): Target basis names to unroll to, e.g. `['u3', 'cx']`.
target (Target): The option :class:`~.Target` object that is the target
of compilation. If both this and ``basis_gates`` are specified, ``basis_gates``
will be ignored.
"""

super().__init__()
self._equiv_lib = equivalence_library
self._basis_gates = basis_gates
self._target = target

def run(self, dag):
"""Run the UnrollCustomDefinitions pass on `dag`.
Expand All @@ -50,11 +54,13 @@ def run(self, dag):
DAGCircuit: output unrolled dag
"""

if self._basis_gates is None:
if self._basis_gates is None and self._target is None:
return dag

basic_insts = {"measure", "reset", "barrier", "snapshot", "delay"}
device_insts = basic_insts | set(self._basis_gates)
if self._target is None:
basic_insts = {"measure", "reset", "barrier", "snapshot", "delay"}
device_insts = basic_insts | set(self._basis_gates)
qubit_mapping = {bit: idx for idx, bit in enumerate(dag.qubits)}

for node in dag.op_nodes():
if isinstance(node.op, ControlFlowOp):
Expand All @@ -67,11 +73,22 @@ def run(self, dag):
if dag.has_calibration_for(node):
continue

if node.name in device_insts or self._equiv_lib.has_entry(node.op):
if isinstance(node.op, ControlledGate) and node.op._open_ctrl:
pass
else:
continue
if self._target is not None:
inst_supported = self._target.instruction_supported(
operation_name=node.op.name, qargs=tuple(qubit_mapping[x] for x in node.qargs)
)
if inst_supported or self._equiv_lib.has_entry(node.op):
if isinstance(node.op, ControlledGate) and node.op._open_ctrl:
pass
else:
continue

else:
if node.name in device_insts or self._equiv_lib.has_entry(node.op):
if isinstance(node.op, ControlledGate) and node.op._open_ctrl:
pass
else:
continue

try:
rule = node.op.definition.data
Expand All @@ -93,9 +110,9 @@ def run(self, dag):
"and no rule found to expand." % (str(self._basis_gates), node.op.name)
)
decomposition = circuit_to_dag(node.op.definition)
unrolled_dag = UnrollCustomDefinitions(self._equiv_lib, self._basis_gates).run(
decomposition
)
unrolled_dag = UnrollCustomDefinitions(
self._equiv_lib, self._basis_gates, target=self._target
).run(decomposition)
dag.substitute_node_with_dag(node, unrolled_dag)

return dag
2 changes: 1 addition & 1 deletion qiskit/transpiler/preset_passmanagers/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def generate_translation_passmanager(
target=target,
),
HighLevelSynthesis(),
UnrollCustomDefinitions(sel, basis_gates),
UnrollCustomDefinitions(sel, basis_gates=basis_gates, target=target),
BasisTranslator(sel, basis_gates, target),
]
elif method == "synthesis":
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
features:
- |
The :class:`~.UnrollCustomDefinitions` transpiler pass has a new keyword
argument, ``target``, on it's constructor. This argument can be used to
specify a :class:`~.Target` object that represnts the compilation target.
If used it superscedes the ``basis_gates`` argument to determine if an
instruction in the circuit is present on the target backend.
2 changes: 1 addition & 1 deletion test/python/compiler/test_transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1437,7 +1437,7 @@ def test_target_ideal_gates(self, opt_level):
theta = Parameter("θ")
phi = Parameter("ϕ")
lam = Parameter("λ")
target = Target(2)
target = Target(num_qubits=2)
target.add_instruction(UGate(theta, phi, lam))
target.add_instruction(CXGate())
target.add_instruction(Measure())
Expand Down
98 changes: 97 additions & 1 deletion test/python/transpiler/test_unroll_custom_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
from qiskit.transpiler.passes.basis import UnrollCustomDefinitions

from qiskit.test import QiskitTestCase
from qiskit.circuit import EquivalenceLibrary, Gate, Qubit, Clbit
from qiskit.circuit import EquivalenceLibrary, Gate, Qubit, Clbit, Parameter
from qiskit.circuit import QuantumCircuit, QuantumRegister
from qiskit.converters import circuit_to_dag
from qiskit.exceptions import QiskitError
from qiskit.transpiler import Target
from qiskit.circuit.library import CXGate, U3Gate


class TestGate(Gate):
Expand Down Expand Up @@ -205,3 +207,97 @@ def test_nested_control_flow(self):
expected.if_else(range(2), pass_(expected_if_body), None, [0], [0])

self.assertEqual(pass_(test), expected)

def test_dont_unroll_a_gate_in_basis_gates_with_target(self):
"""Verify we don't unroll a gate in basis_gates."""
eq_lib = EquivalenceLibrary()

gate = TestGate()
qc = QuantumCircuit(1)
qc.append(gate, [0])

dag = circuit_to_dag(qc)
target = Target(num_qubits=1)
target.add_instruction(U3Gate(Parameter("a"), Parameter("b"), Parameter("c")))
target.add_instruction(CXGate())
target.add_instruction(TestGate())

out = UnrollCustomDefinitions(eq_lib, target=target).run(dag)

expected = qc.copy()
expected_dag = circuit_to_dag(expected)

self.assertEqual(out, expected_dag)

def test_raise_for_opaque_not_in_eq_lib_target_with_target(self):
"""Verify we raise for an opaque gate not in basis_gates or eq_lib."""
eq_lib = EquivalenceLibrary()

gate = TestGate()
qc = QuantumCircuit(1)
qc.append(gate, [0])
target = Target(num_qubits=1)
target.add_instruction(U3Gate(Parameter("a"), Parameter("b"), Parameter("c")))
target.add_instruction(CXGate())

dag = circuit_to_dag(qc)
with self.assertRaisesRegex(QiskitError, "Cannot unroll"):
UnrollCustomDefinitions(eq_lib, target=target).run(dag)

def test_unroll_gate_until_reach_basis_gates_with_target(self):
"""Verify we unroll gates until we hit basis_gates."""
eq_lib = EquivalenceLibrary()

gate = TestCompositeGate()
q = QuantumRegister(1, "q")
gate.definition = QuantumCircuit(q)
gate.definition.append(TestGate(), [q[0]], [])

qc = QuantumCircuit(q)
qc.append(gate, [0])

target = Target(num_qubits=1)
target.add_instruction(U3Gate(Parameter("a"), Parameter("b"), Parameter("c")))
target.add_instruction(CXGate())
target.add_instruction(TestGate())

dag = circuit_to_dag(qc)
out = UnrollCustomDefinitions(eq_lib, target=target).run(dag)

expected = QuantumCircuit(1)
expected.append(TestGate(), [0])
expected_dag = circuit_to_dag(expected)

self.assertEqual(out, expected_dag)

def test_unroll_twice_until_we_get_to_eqlib_with_target(self):
"""Verify we unroll gates until we hit basis_gates."""
eq_lib = EquivalenceLibrary()

base_gate = TestGate()
equiv = QuantumCircuit(1)
equiv.h(0)

eq_lib.add_equivalence(base_gate, equiv)

gate = TestCompositeGate()

q = QuantumRegister(1, "q")
gate.definition = QuantumCircuit(q)
gate.definition.append(TestGate(), [q[0]], [])

qc = QuantumCircuit(1)
qc.append(gate, [0])

target = Target(num_qubits=1)
target.add_instruction(U3Gate(Parameter("a"), Parameter("b"), Parameter("c")))
target.add_instruction(CXGate())

dag = circuit_to_dag(qc)
out = UnrollCustomDefinitions(eq_lib, target=target).run(dag)

expected = QuantumCircuit(1)
expected.append(TestGate(), [0])
expected_dag = circuit_to_dag(expected)

self.assertEqual(out, expected_dag)

0 comments on commit ea51a2e

Please sign in to comment.