Skip to content

Commit

Permalink
Make GatesInBasis transpiler pass Target aware (#7548)
Browse files Browse the repository at this point in the history
* Make GatesInBasis transpiler pass Target aware

This commit makes the GatesInBasis transpiler pass target aware. It adds
a new kwarg, target, which optionally takes a Target object. If it's
specified the basis_gates required field will be ignored and the pass
will check that the gates and their qargs are valid in the target.

Part of #7113

* Update qiskit/transpiler/passes/utils/gates_basis.py

Co-authored-by: John Lapeyre <jlapeyre@users.noreply.github.com>

* No longer make basis_gates a required argument

Now that we can specify a target or basis_gates it's probably best that
we don't require basis_gates is always set, especially since a target
will always take precedence over the basis_gates list. This commit makes
basis_gates optional, but explicitly raises an error if neither
basis_gates or target is set on the constructor.

* Switch to op_nodes() for internal loop

This commit tweaks the internal loop to be over dag.op_nodes() instead
of dag.topological_op_nodes() which was used in earlier iterations. In
local benchmarking dag.op_nodes() is on average 2x faster than
dag.topological_op_nodes().

* Improve performance of basis_gates path

This commit improves the performance of the basis_gates code path by
combining the basis_gates set with the built-in directives and doing
only one set lookup per iteration instead of two.

Co-authored-by: John Lapeyre <jlapeyre@users.noreply.github.com>

* Fix support of ideal operations in target

This commit fixes the handling of ideal operations in a target. If an
operation is listed as ideal (where it applies on all qubits and doesn't
have any error or duration information) in a target then the instruction
properties inner dict is set to `{None: None}` to indicate this. For the
gates in basis pass this just means it needs to check if the qubits for
a particular instruction are None, and if it is we can treat it as a
match.

* Use target instruction_supported() method

In #7778 a new target method, instruction_supported(), was added to
simplify checking if an instruction (operation + qubits) is supported on
a target object. This commit switches the GatesInBasis pass to use this
internally instead of manually querying the target.

Co-authored-by: John Lapeyre <jlapeyre@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Mar 30, 2022
1 parent 25be8a2 commit c28fe7b
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 8 deletions.
40 changes: 32 additions & 8 deletions qiskit/transpiler/passes/utils/gates_basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,51 @@
"""Check if all gates in the DAGCircuit are in the specified basis gates."""

from qiskit.transpiler.basepasses import AnalysisPass
from qiskit.transpiler.exceptions import TranspilerError


class GatesInBasis(AnalysisPass):
"""Check if all gates in a DAG are in a given set of gates"""

def __init__(self, basis_gates):
def __init__(self, basis_gates=None, target=None):
"""Initialize the GatesInBasis pass.
Args:
basis_gates (list): The list of strings representing the set of basis gates.
target (Target): The target representing the backend. If specified
this will be used instead of the ``basis_gates`` parameter
Raises:
TranspilerError: If neither basis_gates or target is set.
"""
super().__init__()
self._basis_gates = set(basis_gates)
if basis_gates is None and target is None:
raise TranspilerError(
"A value for 'basis_gates' or 'target' must be set to use this pass"
)
if basis_gates is not None:
self._basis_gates = set(basis_gates).union(
{"measure", "reset", "barrier", "snapshot", "delay"}
)
self._target = target

def run(self, dag):
"""Run the GatesInBasis pass on `dag`."""
gates_out_of_basis = False
basic_instrs = {"measure", "reset", "barrier", "snapshot", "delay"}
for gate in dag._op_names:
if gate not in self._basis_gates and gate not in basic_instrs:
gates_out_of_basis = True
break

if self._target is not None:
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
else:
for gate in dag._op_names:
if gate not in self._basis_gates:
gates_out_of_basis = True
break
self.property_set["all_gates_in_basis"] = not gates_out_of_basis
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
features:
- |
Added a new kwarg, ``target``, to the constructor for the
:class:`.GatesInBasis` transpiler pass. This new argument can be used to
optionally specify a :class:`.Target` object that represents the backend.
When set this :class:`.Target` will be used for determining whether
a :class:`.DAGCircuit` contains gates outside the basis set.
118 changes: 118 additions & 0 deletions test/python/transpiler/test_gates_in_basis_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@
"""Test GatesInBasis pass."""

from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library import HGate, CXGate, UGate
from qiskit.circuit.measure import Measure
from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import BasisTranslator
from qiskit.transpiler.passes import GatesInBasis
from qiskit.transpiler.target import Target
from qiskit.test import QiskitTestCase
from qiskit.test.mock.fake_backend_v2 import FakeBackend5QV2


class TestGatesInBasisPass(QiskitTestCase):
Expand Down Expand Up @@ -88,3 +92,117 @@ def test_all_gates_in_basis_after_translation(self):
pm.append(analysis_pass)
pm.run(circuit)
self.assertTrue(pm.property_set["all_gates_in_basis"])

def test_all_gates_in_basis_with_target(self):
"""Test circuit with all gates in basis with target."""
target = FakeBackend5QV2().target
basis_gates = ["cx", "u"] # not used
property_set = {}
analysis_pass = GatesInBasis(basis_gates, target=target)
circuit = QuantumCircuit(2)
circuit.u(0, 0, 0, 0)
circuit.cx(0, 1)
circuit.measure_all()
analysis_pass(circuit, property_set=property_set)
self.assertTrue(property_set["all_gates_in_basis"])

def test_all_gates_not_in_basis_with_target(self):
"""Test circuit with not all gates in basis with target."""
target = FakeBackend5QV2().target
basis_gates = ["cx", "h"]
property_set = {}
analysis_pass = GatesInBasis(basis_gates, target=target)
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
analysis_pass(circuit, property_set=property_set)
self.assertFalse(property_set["all_gates_in_basis"])

def test_all_gates_in_basis_not_on_all_qubits_with_target(self):
"""Test circuit with gate in global basis but not local basis."""
target = FakeBackend5QV2().target
basis_gates = ["ecr", "cx", "h"]
property_set = {}
analysis_pass = GatesInBasis(basis_gates, target=target)
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.ecr(0, 1)
circuit.measure_all()
analysis_pass(circuit, property_set=property_set)
self.assertFalse(property_set["all_gates_in_basis"])

def test_all_gates_in_basis_empty_circuit_with_target(self):
"""Test circuit with no gates with target."""
target = FakeBackend5QV2().target
basis_gates = ["cx", "u"]
property_set = {}
analysis_pass = GatesInBasis(basis_gates, target=target)
circuit = QuantumCircuit(2)
analysis_pass(circuit, property_set=property_set)
self.assertTrue(property_set["all_gates_in_basis"])

def test_all_gates_in_empty_target(self):
"""Test circuit with gates and empty basis with target."""
target = Target()
basis_gates = []
property_set = {}
analysis_pass = GatesInBasis(basis_gates, target=target)
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
analysis_pass(circuit, property_set=property_set)
self.assertFalse(property_set["all_gates_in_basis"])

def test_all_gates_in_ideal_sim_target(self):
"""Test with target that has ideal gates."""
target = Target(num_qubits=2)
target.add_instruction(HGate())
target.add_instruction(CXGate())
target.add_instruction(Measure())
property_set = {}
analysis_pass = GatesInBasis(target=target)
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
analysis_pass(circuit, property_set=property_set)
self.assertTrue(property_set["all_gates_in_basis"])

def test_all_gates_not_in_ideal_sim_target(self):
"""Test with target that has ideal gates."""
target = Target()
target.add_instruction(HGate())
target.add_instruction(UGate(0, 0, 0))
target.add_instruction(Measure())
property_set = {}
analysis_pass = GatesInBasis(target=target)
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
analysis_pass(circuit, property_set=property_set)
self.assertFalse(property_set["all_gates_in_basis"])

def test_all_gates_in_basis_after_translation_with_target(self):
"""Test circuit with gates in basis after conditional translation."""
target = FakeBackend5QV2().target
basis_gates = ["cx", "u"]
property_set = {}
analysis_pass = GatesInBasis(basis_gates, target)
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
analysis_pass(circuit, property_set=property_set)
self.assertFalse(property_set["all_gates_in_basis"])
pm = PassManager()
pm.append(analysis_pass)
pm.append(
BasisTranslator(SessionEquivalenceLibrary, basis_gates, target=target),
condition=lambda property_set: not property_set["all_gates_in_basis"],
)
pm.append(analysis_pass)
pm.run(circuit)
self.assertTrue(pm.property_set["all_gates_in_basis"])

0 comments on commit c28fe7b

Please sign in to comment.