From 890f4503c55ad7ff51396afddbbc1f9a926662ae Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Sat, 1 Oct 2022 07:16:37 +0100 Subject: [PATCH] Add control-flow support to `GatesInBasis` (#8823) It's not yet entirely clear how control-flow will be supported through the `Target` and `basis_gates`, but for the time being, we just test them as if they will be added like any other instruction. This is unlikely to entirely remain the case as more classical handling is added. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/transpiler/passes/utils/gates_basis.py | 36 ++++++++--- .../transpiler/test_gates_in_basis_pass.py | 61 ++++++++++++++++++- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/qiskit/transpiler/passes/utils/gates_basis.py b/qiskit/transpiler/passes/utils/gates_basis.py index 06943ea2d652..657b1d134852 100644 --- a/qiskit/transpiler/passes/utils/gates_basis.py +++ b/qiskit/transpiler/passes/utils/gates_basis.py @@ -12,6 +12,8 @@ """Check if all gates in the DAGCircuit are in the specified basis gates.""" +from qiskit.circuit import ControlFlowOp +from qiskit.converters import circuit_to_dag from qiskit.transpiler.basepasses import AnalysisPass @@ -41,18 +43,32 @@ def run(self, dag): return gates_out_of_basis = False if self._target is not None: + + def _visit_target(dag, wire_map): + for gate in dag.op_nodes(): + # Barrier is universal and supported by all backends + if gate.name == "barrier": + continue + if not self._target.instruction_supported( + gate.name, tuple(wire_map[bit] for bit in gate.qargs) + ): + return True + # Control-flow ops still need to be supported, so don't skip them in the + # previous checks. + if isinstance(gate.op, ControlFlowOp): + for block in gate.op.blocks: + inner_wire_map = { + inner: wire_map[outer] + for outer, inner in zip(gate.qargs, block.qubits) + } + if _visit_target(circuit_to_dag(block), inner_wire_map): + return True + return False + qubit_map = {qubit: index for index, qubit in enumerate(dag.qubits)} - for gate in dag.op_nodes(): - # Barrier is universal and supported by all backends - if gate.name == "barrier": - continue - if not self._target.instruction_supported( - gate.name, tuple(qubit_map[bit] for bit in gate.qargs) - ): - gates_out_of_basis = True - break + gates_out_of_basis = _visit_target(dag, qubit_map) else: - for gate in dag._op_names: + for gate in dag.count_ops(recurse=True): if gate not in self._basis_gates: gates_out_of_basis = True break diff --git a/test/python/transpiler/test_gates_in_basis_pass.py b/test/python/transpiler/test_gates_in_basis_pass.py index 929ee7c89d93..bae16c921582 100644 --- a/test/python/transpiler/test_gates_in_basis_pass.py +++ b/test/python/transpiler/test_gates_in_basis_pass.py @@ -12,8 +12,8 @@ """Test GatesInBasis pass.""" -from qiskit.circuit import QuantumCircuit -from qiskit.circuit.library import HGate, CXGate, UGate +from qiskit.circuit import QuantumCircuit, ForLoopOp, IfElseOp, Clbit +from qiskit.circuit.library import HGate, CXGate, UGate, XGate, ZGate from qiskit.circuit.measure import Measure from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary from qiskit.transpiler import PassManager @@ -206,3 +206,60 @@ def test_all_gates_in_basis_after_translation_with_target(self): pm.append(analysis_pass) pm.run(circuit) self.assertTrue(pm.property_set["all_gates_in_basis"]) + + def test_basis_gates_control_flow(self): + """Test that the pass recurses into control flow.""" + circuit = QuantumCircuit(4, 1) + circuit.h(0) + circuit.measure(0, 0) + with circuit.for_loop((1, 2)): + circuit.cx(0, 1) + with circuit.if_test((circuit.clbits[0], True)) as else_: + circuit.x(2) + with else_: + circuit.z(3) + + one_missing = {"h", "measure", "for_loop", "cx", "if_else", "x"} + pass_ = GatesInBasis(one_missing) + pass_(circuit) + self.assertFalse(pass_.property_set["all_gates_in_basis"]) + + complete = one_missing | {"z"} + pass_ = GatesInBasis(complete) + pass_(circuit) + self.assertTrue(pass_.property_set["all_gates_in_basis"]) + + def test_basis_gates_target(self): + """Test that the pass recurses into control flow.""" + circuit = QuantumCircuit(4, 1) + circuit.h(0) + circuit.measure(0, 0) + with circuit.for_loop((1, 2)): + circuit.cx(0, 1) + with circuit.if_test((circuit.clbits[0], True)) as else_: + circuit.x(2) + with else_: + circuit.z(3) + + instructions = [ + HGate(), + Measure(), + ForLoopOp((), None, QuantumCircuit(4)), + CXGate(), + IfElseOp((Clbit(), True), QuantumCircuit(2), QuantumCircuit(2)), + XGate(), + ZGate(), + ] + one_missing = Target(num_qubits=4) + for instruction in instructions[:-1]: + one_missing.add_instruction(instruction, {None: None}) + pass_ = GatesInBasis(target=one_missing) + pass_(circuit) + self.assertFalse(pass_.property_set["all_gates_in_basis"]) + + complete = Target(num_qubits=4) + for instruction in instructions: + complete.add_instruction(instruction, {None: None}) + pass_ = GatesInBasis(target=complete) + pass_(circuit) + self.assertTrue(pass_.property_set["all_gates_in_basis"])